github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/integration/protocols/netpipe/synchronization.go (about) 1 package netpipe 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 8 "github.com/mutagen-io/mutagen/pkg/logging" 9 "github.com/mutagen-io/mutagen/pkg/synchronization" 10 "github.com/mutagen-io/mutagen/pkg/synchronization/endpoint/remote" 11 urlpkg "github.com/mutagen-io/mutagen/pkg/url" 12 ) 13 14 // synchronizationProtocolHandler implements the synchronization.ProtocolHandler 15 // interface for connecting to "remote" endpoints that actually exist in memory 16 // via an in-memory pipe. 17 type synchronizationProtocolHandler struct{} 18 19 // waitingSynchronizationEndpoint wraps and implements synchronization.Endpoint, 20 // but adds a waiting function that's invoked after invoking Shutdown on the 21 // underlying endpoint. It is necessary to ensure full endpoint shutdown in 22 // tests, where open file descriptors or handles can prevent temporary directory 23 // removal. 24 type waitingSynchronizationEndpoint struct { 25 // Endpoint is the underlying endpoint. 26 synchronization.Endpoint 27 // wait is an arbitrary waiting function. 28 wait func() 29 } 30 31 // Shutdown implements synchronization.Endpoint.Shutdown. 32 func (w *waitingSynchronizationEndpoint) Shutdown() error { 33 // Shutdown on the underlying endpoint. 34 result := w.Endpoint.Shutdown() 35 36 // Perform the wait operation. 37 w.wait() 38 39 // Done. 40 return result 41 } 42 43 // Dial starts an endpoint server in a background Goroutine and creates an 44 // endpoint client connected to the server via an in-memory connection. 45 func (h *synchronizationProtocolHandler) Connect( 46 _ context.Context, 47 logger *logging.Logger, 48 url *urlpkg.URL, 49 prompter string, 50 session string, 51 version synchronization.Version, 52 configuration *synchronization.Configuration, 53 alpha bool, 54 ) (synchronization.Endpoint, error) { 55 // Verify that the URL is of the correct kind and protocol. 56 if url.Kind != urlpkg.Kind_Synchronization { 57 panic("non-synchronization URL dispatched to synchronization protocol handler") 58 } else if url.Protocol != Protocol_Netpipe { 59 panic("non-netpipe URL dispatched to netpipe protocol handler") 60 } 61 62 // Create an in-memory network connection. 63 clientConnection, serverConnection := net.Pipe() 64 65 // Serve the endpoint in a background Goroutine. This will terminate once 66 // the client connection is closed. We monitor for its termination so that 67 // we can block on it in our endpoint wrapper. 68 remoteEndpointDone := make(chan struct{}) 69 go func() { 70 remote.ServeEndpoint(logger.Sublogger("remote"), serverConnection) 71 close(remoteEndpointDone) 72 }() 73 74 // Create a client for this endpoint. 75 endpoint, err := remote.NewEndpoint( 76 logger, 77 clientConnection, 78 url.Path, 79 session, 80 version, 81 configuration, 82 alpha, 83 ) 84 if err != nil { 85 return nil, fmt.Errorf("unable to create in-memory endpoint client: %w", err) 86 } 87 88 // Wrap the client so that it blocks on the full shutdown of the remote 89 // endpoint after closing the connection. This is necessary for testing, 90 // where we need to ensure that all file descriptors or handles point to 91 // temporary test directories are closed before attempting to remove those 92 // directories. This is not necessary for other remote protocols in normal 93 // usage (because we don't have the same constraints) or in testing (because 94 // the underlying connection closure waits for agent process termination). 95 endpoint = &waitingSynchronizationEndpoint{endpoint, func() { <-remoteEndpointDone }} 96 97 // Success. 98 return endpoint, nil 99 } 100 101 func init() { 102 // Register the netpipe protocol handler with the synchronization package. 103 synchronization.ProtocolHandlers[Protocol_Netpipe] = &synchronizationProtocolHandler{} 104 }