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  }