github.com/jmigpin/editor@v1.6.0/core/godebug/client.go (about)

     1  package godebug
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/jmigpin/editor/core/godebug/debug"
    12  )
    13  
    14  type Client struct {
    15  	Conn     net.Conn
    16  	Messages chan interface{}
    17  	waitg    sync.WaitGroup
    18  }
    19  
    20  func NewClient(ctx context.Context, network, addr string) (*Client, error) {
    21  	client := &Client{
    22  		Messages: make(chan interface{}, 64),
    23  	}
    24  	if err := client.connect(ctx, network, addr); err != nil {
    25  		return nil, fmt.Errorf("client connect: %w", err)
    26  	}
    27  
    28  	// ensure connection close on ctx cancel
    29  	go func() {
    30  		select {
    31  		case <-ctx.Done():
    32  			client.Conn.Close()
    33  		}
    34  	}()
    35  
    36  	// receive msgs from server and send to channel
    37  	client.waitg.Add(1)
    38  	go func() {
    39  		defer client.waitg.Done()
    40  		client.receiveLoop()
    41  	}()
    42  
    43  	return client, nil
    44  }
    45  
    46  func (client *Client) Wait() {
    47  	client.waitg.Wait()
    48  }
    49  
    50  func (client *Client) Close() error {
    51  	if client.Conn != nil {
    52  		return client.Conn.Close()
    53  	}
    54  	return nil
    55  }
    56  
    57  func (client *Client) connect(ctx context.Context, network, addr string) error {
    58  	// timeout to get a sucessful connection
    59  	ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
    60  	defer cancel()
    61  
    62  	retry := 250 * time.Millisecond
    63  	for {
    64  		dialer := &net.Dialer{}
    65  		dialer.Timeout = retry
    66  		conn, err := dialer.DialContext(ctx, network, addr)
    67  		if err == nil {
    68  			client.Conn = conn
    69  			return nil
    70  		}
    71  		select {
    72  		case <-ctx.Done():
    73  			return ctx.Err()
    74  		default: // non-blocking
    75  			if retry < 5*time.Second {
    76  				retry += retry // increase for slower conns
    77  			}
    78  		}
    79  	}
    80  }
    81  
    82  func (client *Client) receiveLoop() {
    83  	defer close(client.Messages)
    84  	for {
    85  		msg, err := debug.DecodeMessage(client.Conn)
    86  		if err != nil {
    87  			// unable to read (server was probably closed)
    88  			if operr, ok := err.(*net.OpError); ok {
    89  				if operr.Op == "read" {
    90  					break
    91  				}
    92  			}
    93  			// connection ended gracefully by the client
    94  			if err == io.EOF {
    95  				break
    96  			}
    97  
    98  			client.Messages <- err
    99  			continue
   100  		}
   101  
   102  		client.Messages <- msg
   103  	}
   104  }