go-hep.org/x/hep@v0.38.1/xrootd/internal/mux/mux.go (about)

     1  // Copyright ©2018 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  Package mux implements the multiplexer that manages access to and writes data to the channels
     7  by corresponding StreamID from xrootd protocol specification.
     8  
     9  Example of usage:
    10  
    11  	mux := New()
    12  	defer m.Close()
    13  
    14  	// Claim channel for response retrieving.
    15  	id, channel, err := m.Claim()
    16  	if err != nil {
    17  		// handle error.
    18  	}
    19  
    20  	// Send a request to the server using id as a streamID.
    21  
    22  	go func() {
    23  		// Read response from the server.
    24  		// ...
    25  
    26  		// Send response to the awaiting caller using streamID from the server.
    27  		err := m.SendData(streamID, want)
    28  		if err != nil {
    29  			// handle error.
    30  		}
    31  	}
    32  
    33  
    34  	// Fetch response.
    35  	response := <-channel
    36  */
    37  package mux // import "go-hep.org/x/hep/xrootd/internal/mux"
    38  
    39  import (
    40  	"encoding/binary"
    41  	"errors"
    42  	"fmt"
    43  	"math"
    44  	"strconv"
    45  	"strings"
    46  	"sync"
    47  
    48  	"go-hep.org/x/hep/xrootd/xrdproto"
    49  )
    50  
    51  // ServerResponse contains slice of bytes Data representing data from
    52  // XRootD server response (see XRootD protocol specification) and
    53  // Err representing error received from server or occurred
    54  // during response decoding.
    55  type ServerResponse struct {
    56  	Data        []byte
    57  	Err         error
    58  	Redirection *Redirection
    59  }
    60  
    61  // Redirection represents the redirection request from the server.
    62  // It contains address addr to which client must connect,
    63  // opaque data that must be delivered to the new server as
    64  // opaque information added to the file name, and token that
    65  // must be delivered to the new server as part of login request.
    66  type Redirection struct {
    67  	// Addr is the server address to which client must connect in the format "host:port".
    68  	Addr string
    69  
    70  	// Opaque is the data that must be delivered to the new server as
    71  	// opaque information added to the file name
    72  	Opaque string
    73  
    74  	// Token is the data that must be delivered to the new server as
    75  	// part of the login request.
    76  	Token string
    77  }
    78  
    79  // ParseRedirection parses the Redirection from the XRootD redirect response format.
    80  // See http://xrootd.org/doc/dev45/XRdv310.pdf, p. 33 for details.
    81  func ParseRedirection(raw []byte) (*Redirection, error) {
    82  	port := binary.BigEndian.Uint32(raw)
    83  	parts := strings.Split(string(raw[4:]), "?")
    84  	if len(parts) == 0 {
    85  		return nil, fmt.Errorf("xrootd: could not parse redirect url %q", string(raw))
    86  	}
    87  
    88  	var opaque, token string
    89  	if len(parts) > 1 {
    90  		opaque = parts[1]
    91  	}
    92  	if len(parts) > 2 {
    93  		token = parts[2]
    94  	}
    95  	addr := parts[0] + ":" + strconv.Itoa(int(port))
    96  	return &Redirection{Addr: addr, Opaque: opaque, Token: token}, nil
    97  }
    98  
    99  type dataSendChan chan<- ServerResponse
   100  type DataRecvChan <-chan ServerResponse
   101  
   102  const streamIDPartSize = math.MaxUint8
   103  const streamIDPoolSize = streamIDPartSize * streamIDPartSize
   104  
   105  // Mux manages channels by their ids.
   106  // Basically, it's a map[StreamID] chan<-ServerResponse
   107  // with methods to claim, free and pass data to a specific channel by id.
   108  type Mux struct {
   109  	mu          sync.Mutex
   110  	dataWaiters map[xrdproto.StreamID]dataSendChan
   111  	freeIDs     chan uint16
   112  	quit        chan struct{}
   113  	closed      bool
   114  }
   115  
   116  // New creates a new Mux.
   117  func New() *Mux {
   118  	const freeIDsBufferSize = 32 // 32 is completely arbitrary ATM and should be refined based on real use cases.
   119  
   120  	m := Mux{
   121  		dataWaiters: make(map[xrdproto.StreamID]dataSendChan),
   122  		freeIDs:     make(chan uint16, freeIDsBufferSize),
   123  		quit:        make(chan struct{}),
   124  	}
   125  
   126  	go func() {
   127  		var i uint16 = 0
   128  		for {
   129  			select {
   130  			case m.freeIDs <- i:
   131  				i = (i + 1) % streamIDPoolSize
   132  			case <-m.quit:
   133  				close(m.freeIDs)
   134  				return
   135  			}
   136  		}
   137  	}()
   138  
   139  	return &m
   140  }
   141  
   142  // Close closes the Mux.
   143  func (m *Mux) Close() {
   144  	m.mu.Lock()
   145  	if m.closed {
   146  		m.mu.Unlock()
   147  		return
   148  	}
   149  	m.closed = true
   150  	m.mu.Unlock()
   151  	close(m.quit)
   152  
   153  	response := ServerResponse{Err: errors.New("xrootd: close was called before response was fully received")}
   154  	for streamID := range m.dataWaiters {
   155  		_ = m.SendData(streamID, response)
   156  		m.Unclaim(streamID)
   157  	}
   158  }
   159  
   160  // Claim searches for unclaimed id and returns corresponding channel.
   161  func (m *Mux) Claim() (xrdproto.StreamID, DataRecvChan, error) {
   162  	ch := make(chan ServerResponse)
   163  
   164  	for {
   165  		id := <-m.freeIDs
   166  		streamId := xrdproto.StreamID{byte(id >> 8), byte(id)}
   167  
   168  		m.mu.Lock()
   169  		if m.closed {
   170  			m.mu.Unlock()
   171  			return xrdproto.StreamID{}, nil, errors.New("mux: Claim was called on closed Mux")
   172  		}
   173  		if _, claimed := m.dataWaiters[streamId]; claimed { // Skip id if it was already claimed manually via ClaimWithID
   174  			m.mu.Unlock()
   175  			continue
   176  		}
   177  
   178  		m.dataWaiters[streamId] = ch
   179  		m.mu.Unlock()
   180  		return streamId, ch, nil
   181  	}
   182  }
   183  
   184  // ClaimWithID checks if id is unclaimed and returns the corresponding channel in case of success.
   185  func (m *Mux) ClaimWithID(id xrdproto.StreamID) (DataRecvChan, error) {
   186  	m.mu.Lock()
   187  	defer m.mu.Unlock()
   188  	if m.closed {
   189  		return nil, errors.New("mux: ClaimWithID was called on closed Mux")
   190  	}
   191  	ch := make(chan ServerResponse)
   192  
   193  	if _, claimed := m.dataWaiters[id]; claimed {
   194  		return nil, fmt.Errorf("mux: channel with id %v is already claimed", id)
   195  	}
   196  
   197  	m.dataWaiters[id] = ch
   198  
   199  	return ch, nil
   200  }
   201  
   202  // Unclaim marks channel with specified id as unclaimed.
   203  func (m *Mux) Unclaim(id xrdproto.StreamID) {
   204  	m.mu.Lock()
   205  	defer m.mu.Unlock()
   206  
   207  	if _, ok := m.dataWaiters[id]; ok {
   208  		close(m.dataWaiters[id])
   209  		delete(m.dataWaiters, id)
   210  	}
   211  }
   212  
   213  // SendData sends data to channel with specific id.
   214  func (m *Mux) SendData(id xrdproto.StreamID, data ServerResponse) error {
   215  	m.mu.Lock()
   216  	defer m.mu.Unlock()
   217  
   218  	if _, ok := m.dataWaiters[id]; !ok {
   219  		return fmt.Errorf("mux: cannot find data waiter for id %v", id)
   220  	}
   221  
   222  	m.dataWaiters[id] <- data
   223  
   224  	return nil
   225  }