Cap'n Proto library and code generator for Go

Cap'n Proto bindings for Go

GoDoc License CodeQuality Go

Cap’n Proto is an insanely fast data interchange format similar to Protocol Buffers, but much faster.

It also includes a sophisticated RPC system based on Object Capabilities, ideal for secure, low-latency applications.

This package provides:

  • Go code-generation for Cap'n Proto
  • Runtime support for the Go language
  • Level 1 support for the Cap'n Proto RPC protocol

Support for Level 3 RPC is planned.

Installation

To interact with pre-compiled schemas

Ensure that Go modules are enabled, then run the following command:

$ go get capnproto.org/go/capnp/v3

To compile Cap'n Proto schema files to Go

Two additional steps are needed to compile .capnp files to Go:

  1. Install the Cap'n Proto tools.
  2. Install the Go language bindings by running:
go install capnproto.org/go/capnp/v3/capnpc-go@latest  # install go compiler plugin
GO111MODULE=off go get -u capnproto.org/go/capnp/v3/  # install go-capnproto to $GOPATH

To learn how to compile a simple schema, click here.

This package has been tested with version 0.8.0 of the capnp tool.

Documentation

Getting Started

Read the "Getting Started" guide for a high-level introduction to the package API and workflow.
A minimal working RPC example can be found here.

Browse rest of the Wiki for in depth explanations of concepts, migration guides, and tutorials.

Help and Support

You can find us on Matrix: Go Cap'n Proto

API Reference

Available on GoDoc.

API Compatibility

Until the official Cap'n Proto spec is finalized, this repository should be considered beta software.

We use semantic versioning to track compatibility and signal breaking changes. In the spirit of the Go 1 compatibility guarantee, we will make every effort to avoid making breaking API changes within major version numbers, but nevertheless reserve the right to introduce breaking changes for reasons related to:

  • Security.
  • Changes in the Cap'n Proto specification.
  • Bugs.

An exception to this rule is currently in place for the pogs package, which is relatively new and may change over time. However, its functionality has been well-tested, and breaking changes are relatively unlikely.

Note also we may merge breaking changes to the main branch without notice. Users are encouraged to pin their dependencies to a major version, e.g. using the semver-aware features of go get.

License

MIT - see LICENSE file

Comments
  • Possible race condition when creating RPC connections

    Possible race condition when creating RPC connections

    Hello,

    I'm writing a P2P application in which each host opens both a client and server stream to a remote peer. Here's the code where this happens:

    func (s *edgeSpec) newEdge(c context.Context) *Edge {
    	cconn := rpc.NewConn(rpc.StreamTransport(s.RPCClientStream))
    	client := rpcClient{graph_api.RemoteVertex{Client: cconn.Bootstrap(c)}}
    
    	main := graph_api.RemoteVertex_ServerToClient(rpcServer{s.param})
    	sconn := rpc.NewConn(
    		rpc.StreamTransport(s.RPCServerStream),
    		rpc.MainInterface(main.Client),
    	)
    
            // ...
    }
    

    Immediately after newEdge returns, each peer attempts to call a method of RemoteVertex, however, I get a graph.capnp:RemoteVertex.sample: rpc exception: rpc: target not found error, followed by context canceled on subsequent calls.

    Furthermore, this error appears to be stochastic in nature. Every once in a while, things go as planned, and the calls succeed.

    This makes me suspect a race condition in one of the capnproto2/rpc functions. A likely candidate is rpc.NewConn given that it starts two goroutines. Perhaps my RPC method calls are invoked before these are up and running?

    I will investigate further, but I would very much appreciate any insights (or corrections if there are mistakes in my code).

  • Any better documentation?

    Any better documentation?

    Hi, I'm trying out go-capnproto2 using a simple service that is currently working using protobuf-v3. One of the reasons protobuf doesn't work for me is the lack of constants and the requirement of using a different port for every single service. UDS could work, but the memmap and pipes communication in go-capnproto is attractive.

    Following the example on the wiki here: https://github.com/capnproto/go-capnproto2/wiki/Getting-Started#rpc-serving, I get stuck on writing the server adapter. Up to this point my own code works including compilation of the .capnp file, importing it in my code, writing an adapter interface. Using the adapter pattern makes a lot of sense.

    Implementing the adapter itself however makes no sense at all. I've looked for documentation on how to implement a service but other than the example that has little explanation there isn't much. I tried reading the generated code but am a bit overwhelmed where the capnp file of 30 lines explodes to 890 lines of go code.

    Specifically the following example bits are confusing:

    1. Section RPC Serving:
    func serveLogger(ctx context.Context, rwc io.ReadWriteCloser) error {
    	// Create a new locally implemented logger.
    	main := pkg.Logger_ServerToClient(&loggerAdapterServer{
    		internalLogger: &internalLogger{logger: log.New(os.Stdout, "", log.LstdFlags)},
    	}, &server.Policy{MaxConcurrentCalls: 250})
        ...
    

    The function 'pkg.Logger_ServerToClient' seems to come out of nowhere. Why is it here? It seems to be related to the client but it isn't clear how and why. Any documentation on this apparently essential function?

    1. Implementing the adapter code
    func (las *loggerAdapterServer) Debug(_ context.Context, call pkg.Logger_debug) error {
    	msgPayload, err := call.Args().Msg()
    	if err != nil {
    		return err
    	}
    
    	las.internalLogger.Debug(msgPayload)
    	return nil
    }
    

    While it is obvious that this is the adapter implementation of the Debug logging function, it is a mystery where this API is coming from and how to use it. What is the 'call' parameter? what does it do. Any documentation on how to use it?

    Example code is great and important but it doesn't replace proper documentation that describes what the steps are to implement a server with links to API documentation. Something that does not require reverse engineering the generated code? I can probably guess some stuff and eventually get things working but it feels I'm missing important documentation.

    Any pointers in the right direction would be appreciated.

  • First pass at including the capnproto base schema

    First pass at including the capnproto base schema

    gen.sh is the only bit of this that's done manually; everything else is generated.

    Per the commit message, at some point I'd like to turn this into something than I can reuse for my sandstorm packages without copypasta.

    Also, I noticed there's a little bit of logic in the build system already working with a few of the schemas (schema.capnp and rpc.capnp), but wasn't entirely sure how to go about integrating those.

  • Add basic GitHub Actions configuration

    Add basic GitHub Actions configuration

    As per previous discussion, here is a slightly modified version of the default Go configuration for GitHub Actions. It makes the following changes:

    • Run actions against Go 1.8 (since transport behavior changes slightly as of 1.9, and we have tests for this)
    • Run actions for pushes & PRs. in v3 branch, since most work is taking place there for the time being.
  • Race condition in capnp

    Race condition in capnp

    Problem: Running go test -race on my modules sometimes fails with a race condition in go-capnproto2's transport.

    To reproduce:

    The offending code was extracted and put into a simpler repo here: github.com/hiveot/racetest. This repo has 2 tests, 'race' and 'state'. They each show different problems, although it is possible that 'race''s problem is the result of simplification.

    Setup the tests:

    git clone github.com/hiveot/racetest
    cd racetest
    make capnp
    

    Race Package

    To reproduce, run this a few times. It usually shows up within 3 attempts:

    go test -race pkg/race/Race_test.go

    Output of 'race':

    henk@msi:~/dev/hiveot/racetest$ go test -race pkg/race/Race_test.go 
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x6eb3ba]
    
    goroutine 148 [running]:
    capnproto.org/go/capnp/v3/rpc/transport.(*ctxReader).leakyRead.func1()
    	/home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/transport/transport.go:348 +0x5a
    created by capnproto.org/go/capnp/v3/rpc/transport.(*ctxReader).leakyRead
    	/home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/transport/transport.go:347 +0x16c
    FAIL	command-line-arguments	3.027s
    FAIL
    

    State Package

    The 'state' package intermittently shows a race. Usually it shows up within 10 attempts.

    go test -race pkg/state/State_test.go

    Output of 'state':

    henk@msi:~/dev/hiveot/racetest$ go test -race pkg/state/State_test.go 
    ==================
    WARNING: DATA RACE
    Read at 0x00c00015e7c0 by goroutine 37:
      capnproto.org/go/capnp/v3.(*Segment).slice()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:49 +0x5a
      capnproto.org/go/capnp/v3.(*Segment).readUint64()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:65 +0x51
      capnproto.org/go/capnp/v3.(*Segment).readRawPointer()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:69 +0x50
      capnproto.org/go/capnp/v3.(*Segment).resolveFarPointer()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:235 +0x4f
      capnproto.org/go/capnp/v3.(*Segment).readPtr()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:116 +0x75
      capnproto.org/go/capnp/v3.Struct.Ptr()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/struct.go:109 +0x119
      capnproto.org/go/capnp/v3.Transform()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:548 +0x2ed
      capnproto.org/go/capnp/v3.resolution.ptr()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:574 +0xfc
      capnproto.org/go/capnp/v3.resolution.client()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:589 +0x96
      capnproto.org/go/capnp/v3.(*Answer).PipelineRecv()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:375 +0x3f4
      capnproto.org/go/capnp/v3.(*Answer).PipelineRecv-fm()
          <autogenerated>:1 +0xc7
      capnproto.org/go/capnp/v3/server.queueCaller.PipelineRecv()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/answer.go:162 +0x2c2
      capnproto.org/go/capnp/v3/server.(*answerQueue).PipelineRecv()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/answer.go:136 +0xc9
      capnproto.org/go/capnp/v3/rpc.(*Conn).handleCall()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:795 +0x19b2
      capnproto.org/go/capnp/v3/rpc.(*Conn).receive()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:491 +0x40e
      capnproto.org/go/capnp/v3/rpc.(*Conn).receive-fm()
          <autogenerated>:1 +0x39
      capnproto.org/go/capnp/v3/rpc.(*Conn).backgroundTask.func1()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:191 +0x92
      golang.org/x/sync/errgroup.(*Group).Go.func1()
          /home/henk/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:57 +0x91
    
    Previous write at 0x00c00015e7c0 by goroutine 28:
      capnproto.org/go/capnp/v3.alloc()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/message.go:351 +0x1d1
      capnproto.org/go/capnp/v3.NewCompositeList()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/list.go:62 +0xe4
      capnproto.org/go/capnp/v3/std/capnp/rpc.NewCapDescriptor_List()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/std/capnp/rpc/rpc.capnp.go:2443 +0xce
      capnproto.org/go/capnp/v3/std/capnp/rpc.Payload.NewCapTable()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/std/capnp/rpc/rpc.capnp.go:2215 +0x84
      capnproto.org/go/capnp/v3/rpc.(*Conn).fillPayloadCapTable()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/export.go:183 +0x14f
      capnproto.org/go/capnp/v3/rpc.(*answer).sendReturn()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/answer.go:236 +0x198
      capnproto.org/go/capnp/v3/rpc.(*answer).Return()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/answer.go:201 +0x291
      capnproto.org/go/capnp/v3/server.(*Server).handleCall()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:217 +0x287
      capnproto.org/go/capnp/v3/server.(*Server).handleCalls.func2()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:182 +0x84
      capnproto.org/go/capnp/v3/server.(*Server).handleCalls()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:183 +0x1b6
      capnproto.org/go/capnp/v3/server.New.func1()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:122 +0x58
    
    Goroutine 37 (running) created at:
      golang.org/x/sync/errgroup.(*Group).Go()
          /home/henk/go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:54 +0xee
      capnproto.org/go/capnp/v3/rpc.NewConn()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:157 +0x7d1
      racetest/pkg/caphelp.CapServe.func2()
          /home/henk/dev/hiveot/racetest/pkg/caphelp/CapServe.go:53 +0x2c4
    
    Goroutine 28 (running) created at:
      capnproto.org/go/capnp/v3/server.New()
          /home/henk/go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:122 +0x594
      racetest/capnp/go/api.CapState_NewServer()
          /home/henk/dev/hiveot/racetest/capnp/go/api/State.capnp.go:63 +0xc9
      racetest/capnp/go/api.CapState_ServerToClient()
          /home/henk/dev/hiveot/racetest/capnp/go/api/State.capnp.go:69 +0x36
      racetest/pkg/state/capnpserver.StartStateCapnpServer()
          /home/henk/dev/hiveot/racetest/pkg/state/capnpserver/StateCapnpServer.go:50 +0x166
      command-line-arguments_test.createStateStore.func1()
          /home/henk/dev/hiveot/racetest/pkg/state/State_test.go:37 +0x6f
    ==================
    --- FAIL: TestGetSet (1.02s)
        testing.go:1312: race detected during execution of test
    FAIL
    FAIL	command-line-arguments	3.035s
    FAIL
    
    
  • Merge v3 into master

    Merge v3 into master

    As per previous discussion in #165, I am opening this issue to track discussion about merging v3 into master.

    General Overview

    Our approach is motivated by the fact that

    1. go-capnproto2 uses SemVer tagging
    2. Go module tooling is both git tag and SemVer aware
    3. #169 adds Go module support for v3

    Taken together, these remove the need to enforce backwards-compatibility on the master branch. To put it plainly: users can now use standard module tooling to pin their dependencies to a major version (e.g. v2), and be unaffected by breaking changes pushed to master.

    The README in v3 has been updated to reflect these guarantees.

    Approach

    After discussing with @zenhack, we have settled on the following approach:

    • [x] Tag the current tip of master with a final version-2 tag (v2.18.1)
    • [x] Create a v2 branch for archival and (if required) patch purposes
    • [x] Rebase v3 onto master.
    • [x] Tag the new tip of master with v3.0.0-alpha.1 to denote its unstable state.

    Because the rebase step is fairly complex and will require some manual merging, I have derived a feature/v3 branch from master as a staging branch. Once stabilized, I will open a PR to merge feature/v3 into master. While not strictly necessary, this is a belt-and-suspenders thing.

    Comments are of course welcome.

  • Change import path to capnproto.org/go/capnp

    Change import path to capnproto.org/go/capnp

    Pinging @zombiezen @kentonv .

    As part of https://github.com/capnproto/go-capnproto2/pull/165, it has become necessary to instantiate a go module around this project. @taylorjdawson Initially tried to do the obvious, sensible thing and instantiate it with go mod init github.com/capnproto/go-capnproto2, with predictable results (at the risk of explaining the joke, he should have used zombiezen.com/go/capnproto2). This detail has briefly confused me on occasion as well, and I worry that it might frustrate new Go developers.

    Additionally, many editors are configured to add a package alias when the last segment does not match the package name. Ross recently brought this up during code review and I ended up merging it anyway, after trying to figure out how to edit my VSCode configuration for 20 minutes without success. 😞

    After discussing this in slack with @zenhack and @taylorjdawson, we're of a mind to to change all the import paths in the project from zombiezen.com/go/capnproto2 to (ideally) capnproto.org/go/capnp.

    @zombiezen Is that okay with you? In addition to asking if this might have technical consequences, I wanted to check that you were okay with this first because it's always a bit delicate when the new guys start rebranding something you built!

    @kentonv Assuming ross is onboard with this, would you be willing to add the appropriate <meta> tag so that we could import this project as capnproto.org/go/capnp?

    Miscellaneous thought: I think this is probably safe to do since we're working towards a major version bump anyway.

  • RPC example code appears incorrect

    RPC example code appears incorrect

    When I copy and paste the RPC client example code:

        f, free := hf.NewSha1(ctx, nil)
        defer free()
    
        // 'NewSha1' returns a future, which allows us to pipeline calls to
        // returned values before they are actually delivered.  Here, we issue
        // calls to an as-of-yet-unresolved Sha1 instance.
        s := f.Hash()
    
        // s refers to a remote Hash.  Method calls are delivered in order.
        f, free = s.Write(ctx, func(p hashes.Hash_write_Params) error {
            return p.SetData([]byte("Hello, "))
        })
        defer free()
        f, free = s.Write(ctx, func(p hashes.Hash_write_Params) error {
            return p.SetData([]byte("World!"))
        })
        defer free()
    
        // Get the sum, waiting for the result.
        f, free = s.Sum(ctx, nil)
        defer free()
        result, err := f.Struct()
        if err != nil {
            return err
        }
    
        // Display the result.
        sha1Val, err := result.Hash()
    

    I get lots of errors, since the futures return different types. I tried assigning to different variables which makes the program compile, but it doesn't return a result when I do that.

    ./hashesserver.go:97:10: cannot assign hashes.Hash_write_Results_Future to f (type hashes.HashFactory_newSha1_Results_Future) in multiple assignment
    ./hashesserver.go:97:10: cannot use hashes.Hash_write_Results_Future value as type hashes.HashFactory_newSha1_Results_Future in assignment
    ./hashesserver.go:102:10: cannot assign hashes.Hash_write_Results_Future to f (type hashes.HashFactory_newSha1_Results_Future) in multiple assignment
    ./hashesserver.go:102:10: cannot use hashes.Hash_write_Results_Future value as type hashes.HashFactory_newSha1_Results_Future in assignment
    ./hashesserver.go:108:10: cannot assign hashes.Hash_sum_Results_Future to f (type hashes.HashFactory_newSha1_Results_Future) in multiple assignment
    ./hashesserver.go:108:10: cannot use hashes.Hash_sum_Results_Future value as type hashes.HashFactory_newSha1_Results_Future in assignment
    ./hashesserver.go:119:15: assignment mismatch: 2 variables but result.Hash returns 1 value
    
  • Allow a message to be marked as

    Allow a message to be marked as "safe" to prevent read limiter tracking

    There are cases (both as a feature and as a bug-fix) where read limiter tracking needs to be disabled to prevent hitting traversal limits. Particularly, auto-generated vars not-only hit traversal limits very quickly (if referenced extensively) but also don't need this check since the attack this check is supposed to protect against (amplification attacks), is not a possibilty here.

  • dispatch goroutine leak

    dispatch goroutine leak

    I'm following through the getting started for rpc, and I have managed to work out most of my questions, but I have one that I can't quite work out.

    Say I have some schema like

    interface Database { # the main interface
      dolargequery @0 () -> (cursor :Cursor);
    } 
    interface Cursor {
      next @0 () -> ( data :Data );
    }
    

    And say the client calls dolargequery, the server creates some go struct that, for arguments sake, occupies a bunch of memory and implements Cursor. The server sends the cursor capability back to the client.

    Now my question is how do I idiomatically control the lifetime of the cursor struct? Presumably the server is holding on to a reference to it so that when it receives calls on the capability it can execute them, but how does the client "free" the capability so that the underlying struct I passed to Cursor_ServerToClient can be freed?

    I did some experiments with printing in finalizers (which is always a bit shady) but it doesn't seem that the structure I pass to Cursor_ServerToClient becomes unreferenced even after the rpc connection's Wait() returns.

    How does it all work?

  • Add convenience APIs

    Add convenience APIs

    This is a great library, and I'm getting a lot of use out of it. However, it's very complicated and the learning curve is high. It would be great if portions of the API could be simplified for developers who don't need all features all the time. I'd like to suggest a couple convenience style wrappers for folks who want the benefits but don't need the fine-grained details. I would assume most of the functionality I'm thinking of would happen during code generation, so it wouldn't necessarily require a lot of structural changes.

    While I am raising these as opportunities, I am aware there are potential performance, copying, or reference challenges. I think convenience at the cost of speed is a reasonable trade-off that folks could be willing to make so long as it's documented well, and why. My thought process is, how could the adoption curve be lowered and be as simple as possible? I'm not trying to capture all use cases, just simplify serialization and general struct interactions.

    With a bit of help, I could likely introduce these APIs into the generator.

    Reference

    I'll use the books example for shared context.

    using Go = import "/go.capnp";
    @0x85d3acc39d94e0f8;
    $Go.package("books");
    $Go.import("foo/books");
    
    struct Book {
        title @0 :Text;
        # Title of the book.
    
        pageCount @1 :Int32;
        # Number of pages in the book.
    }
    

    Creation & Marshal

    I think this workflow could be simplified, from this:

    // Make a brand new empty message.  A Message allocates Cap'n Proto structs.
    msg, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
    if err != nil {
        panic(err)
    }
    
    // Create a new Book struct.  Every message must have a root struct.
    book, err := books.NewRootBook(seg)
    if err != nil {
        panic(err)
    }
    book.SetTitle("War and Peace")
    book.SetPageCount(1440)
    
    // Write the message to stdout.
    err = capnp.NewEncoder(os.Stdout).Encode(msg)
    if err != nil {
        panic(err)
    }
    

    Into something more like this:

    // create a new book, create a new root and segment under the hood.
    book, err := books.New()
    if err != nil {
        panic(err)
    }
    
    book.SetTitle("War and Peace")
    book.SetPageCount(1440)
    
    // call `book.Message().Marshal()` behind the scenes
    // or the encoder, whichever is the preferred way
    payload, err := book.Marshal()
    if err != nil {
        panic(err)
    }
    

    I think the segments, messages, structs, and other various construct should still exist, I think this type of simplicity would really benefit a lot of devs without hiding away the different layers.

    Unmarshal and Reference

    Simplifying this:

    // Read the message from stdin.
    msg, err := capnp.NewDecoder(os.Stdin).Decode()
    if err != nil {
        panic(err)
    }
    
    // Extract the root struct from the message.
    book, err := books.ReadRootBook(msg)
    if err != nil {
        panic(err)
    }
    
    // Access fields from the struct.
    title, err := book.Title()
    if err != nil {
        panic(err)
    }
    pageCount := book.PageCount()
    fmt.Printf("%q has %d pages\n", title, pageCount)
    

    Into something like this:

    // implement decoding and extraction under the hood
    book, err := books.Unmarshal(someByteArray)
    if err != nil {
        panic(err)
    }
    
    // Get the field from the struct, don't access it.
    title, err := book.GetTitle()
    if err != nil {
        panic(err)
    }
    pageCount := book.GetPageCount()
    fmt.Printf("%q has %d pages\n", title, pageCount)
    

    In this case, calling capnp.Decoder(io.Reader).Decode() + books.ReadRootBook(msg) is boilerplate code for a lot of use cases. There's definitely a use case for both of those APIs, but a wrapper around them would bring a lot of value.

    I think there's a lot of value in having an explicit foo.GetBar() method instead of just having the current API of foo.Bar(). If you're required to call foo.SetBar(val), it makes sense to have an equivalent construct. Other languages have proper get and set field or property accessors that you can override or implement, and I think the Get()/Set() equality is easier on the brain for most people. Since v3 can include breaking changes, I think it'd be a good time to remove the standard accessor foo.Bar() in favour of foo.GetBar()

  • Fixes wrt. withLocked

    Fixes wrt. withLocked

    The first commit adds a lockedConn parameter to answer.sendException, for symmetry with answer.sendReturn. This resulted in a cascade of type errors, which that commit also fixes.

    While doing that, I noticed a FIXME and decided to just fix it by passing stuff off to a releaseList, in the second commit. Most likely the (now removed) comment was written either when I was in the middle of something else or before releaseLists were clearly the right option for this sort of thing. This potentially removes a deadlock, though I've observed no symptoms.

  • Minor style cleanup

    Minor style cleanup

    Per prior discussion, this expression is doing a bit too much; every time I come across it I have to pick apart what it does again. let's split it out into two statements.

  • Cleanup: tease apart method lookup & server processing.

    Cleanup: tease apart method lookup & server processing.

    Per discussion in #400 and #405, it would be nice to refactor the sever package a bit, so that method handling is encapsulated in an interface like:

    type MethodHandler interface {
        HandleMethod(context.Context, capnp.Method, *Call)
    }
    

    ...and looking up the particular method in a slice or whatnot is an implementation detail of serverMethods's implementation of HandleMethod(). Low priority, since it's mostly a cleanup thing, but I'd like to do this before we tag 3.0 and thus can't make breaking changes.

  • Add handler for unknown method

    Add handler for unknown method

    This is a proposed solution to be able to customize the handling of unknown methods. The default behavior is unchanged but a hook allows customized behavior.

    The hook can be used to dynamically create a method and have it invoked as if it existed all along. I was able to use this in a proxy service that dynamically obtains capabilities/methods from other services and forwards requests to those services.

  • Add Handler for unknown methods

    Add Handler for unknown methods

    Use-case: Proxying RPC requests is needed when the request has to travel through a gateway or via a sidecar. In this case the receiver of the request only has knowledge of the destination that can handle the request and pass it on unchanged.

    The current implementation of server.Server works with a fixed list of methods. To be able to act as a proxy, a handler is needed that is invoked when the requested method is not in the methods list. The default handler can reject the request as it does today. By replacing the handler, other actions can be taken such as forwarding the request.

    The proposed solution is to change Server.Recv to something like this:

    func (srv *Server) Recv(ctx context.Context, r capnp.Recv) capnp.PipelineCaller {
       mm := srv.methods.find(r.Method)
    	if mm == nil {
                    return HandleUnknownMethod(ctx, r)
    	}
    	return srv.start(ctx, mm, r)
    }
    
    func (srv *Server) HandleUnknownMethod(ctx context.Context, r capnp.Recv) capnp.PipelineCaller {
    		r.Reject(capnp.Unimplemented("unimplemented"))
                   return nil
    }
    

    This allows a new Server implementation to define its own HandleUnknownMethod function to forward the request and returns the result.

    There are probably a few pitfalls as to what is allowed in HandleUnknownMethod. I'll test it first to see if this allows the proxying of requests and if that works I'll put a PR in.

  • Data race in message.go

    Data race in message.go

    WARNING: DATA RACE
    Write at 0x00c0014886c8 by goroutine 130:
      capnproto.org/go/capnp/v3.alloc()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/message.go:356 +0x1d2
      capnproto.org/go/capnp/v3.NewCompositeList()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/list.go:62 +0xe4
      capnproto.org/go/capnp/v3/std/capnp/rpc.NewCapDescriptor_List()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/std/capnp/rpc/rpc.capnp.go:2406 +0xce
      capnproto.org/go/capnp/v3/std/capnp/rpc.Payload.NewCapTable()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/std/capnp/rpc/rpc.capnp.go:2179 +0x84
      capnproto.org/go/capnp/v3/rpc.(*Conn).fillPayloadCapTable()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/export.go:183 +0x1fa
      capnproto.org/go/capnp/v3/rpc.(*answer).sendReturn()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/answer.go:228 +0x174
      capnproto.org/go/capnp/v3/rpc.(*answer).Return()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/answer.go:197 +0x11d
      capnproto.org/go/capnp/v3/server.(*Server).handleCall()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:214 +0x281
      capnproto.org/go/capnp/v3/server.(*Server).handleCalls.func2()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:182 +0x84
      capnproto.org/go/capnp/v3/server.(*Server).handleCalls()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:183 +0x1c4
      capnproto.org/go/capnp/v3/server.New.func1()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:122 +0x58
    
    Previous read at 0x00c0014886c8 by goroutine 157:
      capnproto.org/go/capnp/v3.(*Segment).slice()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:49 +0x5d
      capnproto.org/go/capnp/v3.(*Segment).readUint64()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:65 +0x51
      capnproto.org/go/capnp/v3.(*Segment).readRawPointer()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:69 +0x50
      capnproto.org/go/capnp/v3.(*Segment).resolveFarPointer()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:235 +0x4f
      capnproto.org/go/capnp/v3.(*Segment).readPtr()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/segment.go:116 +0x75
      capnproto.org/go/capnp/v3.Struct.Ptr()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/struct.go:109 +0x119
      capnproto.org/go/capnp/v3.Transform()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:580 +0x2ed
      capnproto.org/go/capnp/v3.resolution.ptr()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:606 +0xfc
      capnproto.org/go/capnp/v3.resolution.client()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:615 +0x96
      capnproto.org/go/capnp/v3.(*Answer).PipelineRecv()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/answer.go:393 +0x413
      capnproto.org/go/capnp/v3.(*Answer).PipelineRecv-fm()
          <autogenerated>:1 +0xc7
      capnproto.org/go/capnp/v3/server.queueCaller.PipelineRecv()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/answer.go:161 +0x2e2
      capnproto.org/go/capnp/v3/server.(*answerQueue).PipelineRecv()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/answer.go:136 +0xc9
      capnproto.org/go/capnp/v3/rpc.(*Conn).handleCall()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:837 +0x1f32
      capnproto.org/go/capnp/v3/rpc.(*Conn).receive()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:543 +0x644
      capnproto.org/go/capnp/v3/rpc.(*Conn).receive-fm()
          <autogenerated>:1 +0x39
      capnproto.org/go/capnp/v3/rpc.(*Conn).backgroundTask.func1()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:206 +0x92
      golang.org/x/sync/errgroup.(*Group).Go.func1()
          /Users/lthibault/Go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:75 +0x86
    
    Goroutine 130 (running) created at:
      capnproto.org/go/capnp/v3/server.New()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/server/server.go:122 +0x544
      github.com/wetware/ww/internal/api/pubsub.Router_NewServer()
          /Users/lthibault/Go/src/github.com/wetware/ww/internal/api/pubsub/pubsub.capnp.go:740 +0xc9
      github.com/wetware/ww/internal/api/pubsub.Router_ServerToClient()
          /Users/lthibault/Go/src/github.com/wetware/ww/internal/api/pubsub/pubsub.capnp.go:746 +0x36
      github.com/wetware/ww/pkg/pubsub.NewJoiner()
          /Users/lthibault/Go/src/github.com/wetware/ww/pkg/pubsub/pubsub.go:42 +0x197
      github.com/wetware/ww/pkg/pubsub.(*Server).PubSub()
          /Users/lthibault/Go/src/github.com/wetware/ww/pkg/pubsub/router.go:52 +0x177
      github.com/wetware/ww/pkg/pubsub_test.TestMessageCopy()
          /Users/lthibault/Go/src/github.com/wetware/ww/pkg/pubsub/pubsub_test.go:126 +0xd1
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:1446 +0x216
      testing.(*T).Run.func1()
          /usr/local/go/src/testing/testing.go:1493 +0x47
    
    Goroutine 157 (running) created at:
      golang.org/x/sync/errgroup.(*Group).Go()
          /Users/lthibault/Go/pkg/mod/golang.org/x/[email protected]/errgroup/errgroup.go:72 +0x12e
      capnproto.org/go/capnp/v3/rpc.NewConn()
          /Users/lthibault/Go/pkg/mod/capnproto.org/go/capnp/[email protected]/rpc/rpc.go:172 +0x744
      github.com/wetware/ww/pkg/pubsub_test.TestMessageCopy()
          /Users/lthibault/Go/src/github.com/wetware/ww/pkg/pubsub/pubsub_test.go:130 +0x32c
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:1446 +0x216
      testing.(*T).Run.func1()
          /usr/local/go/src/testing/testing.go:1493 +0x47
    ==================
    
Musgo is a Go code generator for binary MUS format with validation support.

Musgo is a Go code generator for binary MUS format with validation support. Generated code converts data to and from MUS format.

Dec 29, 2022
gogoprotobuf is a fork of golang/protobuf with extra code generation features.

GoGo Protobuf looking for new ownership Protocol Buffers for Go with Gadgets gogoprotobuf is a fork of golang/protobuf with extra code generation feat

Nov 26, 2021
Asn.1 BER and DER encoding library for golang.

WARNING This repo has been archived! NO further developement will be made in the foreseen future. asn1 -- import "github.com/PromonLogicalis/asn1" Pac

Nov 14, 2022
Go library for decoding generic map values into native Go structures and vice versa.

mapstructure mapstructure is a Go library for decoding generic map values to structures and vice versa, while providing helpful error handling. This l

Jan 1, 2023
A go library that facilitates the implementation of decorator pattern.

go-decorator go-decorator is a library that facilitates the implementation of decorator pattern. Installation To install go-decorator, use go get: go

Nov 25, 2021
GED - Global-purpose Encoding / Decoding library

GED - Global-purpose Encoding / Decoding library This library lets you use common encoding/decoding schemes and allows you to define custom ones. Use

Nov 28, 2021
A library that provides dynamic features of Go language.

go-dynamic go-dynamic is a library that provides dynamic features of Go language. Installation To install go-dynamic, use go get: go get -u github.com

Dec 8, 2021
csvutil provides fast and idiomatic mapping between CSV and Go (golang) values.
csvutil provides fast and idiomatic mapping between CSV and Go (golang) values.

csvutil Package csvutil provides fast and idiomatic mapping between CSV and Go (golang) values. This package does not provide a CSV parser itself, it

Jan 6, 2023
Encode and decode binary message and file formats in Go

Encode and Decode Binary Formats in Go This module wraps the package encoding/binary of the Go standard library and provides the missing Marshal() and

Dec 22, 2022
Some Golang types based on builtin. Implements interfaces Value / Scan and MarshalJSON / UnmarshalJSON for simple working with database NULL-values and Base64 encoding / decoding.

gotypes Some simple types based on builtin Golang types that implement interfaces for working with DB (Scan / Value) and JSON (Marshal / Unmarshal). N

Feb 12, 2022
idiomatic codec and rpc lib for msgpack, cbor, json, etc. msgpack.org[Go]

go-codec This repository contains the go-codec library, the codecgen tool and benchmarks for comparing against other libraries. This is a High Perform

Dec 19, 2022
Easily and dynamically generate maps from Go static structures

structomap This package helps you to transform your struct into map easily. It provides a structomap.Serializer interface implemented by the structoma

Dec 9, 2022
Go package for dealing with maps, slices, JSON and other data.

Objx Objx - Go package for dealing with maps, slices, JSON and other data. Get started: Install Objx with one line of code, or update it with another

Dec 27, 2022
Encode and decode Go (golang) struct types via protocol buffers.

protostructure protostructure is a Go library for encoding and decoding a struct type over the wire. This library is useful when you want to send arbi

Nov 15, 2022
Simple, specialised, and efficient binary marshaling

?? surge Documentation A library for fast binary (un)marshaling. Designed to be used in Byzantine networks, ?? surge never explicitly panics, protects

Oct 4, 2022
An optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs
An optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs

TSCrunch TSCrunch is an optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs, while keeping

Dec 21, 2022
🔄 A command-line utility to export Protocol Buffers (proto) files to YAML, and JSON

proto2yaml ?? A command-line utility to export Protocol Buffers (proto) files to YAML, and JSON. Currently supported exports are for: Packages Service

Nov 10, 2022
🔌 RR plugins interfaces and proto API
🔌  RR plugins interfaces and proto API

RoadRunner API RR API consists of 2 parts: Plugin interfaces. Proto API for the PHP clients, at the moment released as V1Beta. Plugins should depend o

Jan 5, 2023
A standard way to wrap a proto message

Welcome to Pletter ?? A standard way to wrap a proto message Pletter was born with a single mission: To standardize wrapping protocol buffer messages.

Nov 17, 2022
Golang module/tool for decoding proto buffer without message definition.
Golang module/tool for decoding proto buffer without message definition.

Golang module/tool for decoding proto buffer without message definition.

Nov 11, 2022