github.com/pion/webrtc/v4@v4.0.1/operations.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  package webrtc
     5  
     6  import (
     7  	"container/list"
     8  	"sync"
     9  )
    10  
    11  // Operation is a function
    12  type operation func()
    13  
    14  // Operations is a task executor.
    15  type operations struct {
    16  	mu     sync.Mutex
    17  	busyCh chan struct{}
    18  	ops    *list.List
    19  
    20  	updateNegotiationNeededFlagOnEmptyChain *atomicBool
    21  	onNegotiationNeeded                     func()
    22  	isClosed                                bool
    23  }
    24  
    25  func newOperations(
    26  	updateNegotiationNeededFlagOnEmptyChain *atomicBool,
    27  	onNegotiationNeeded func(),
    28  ) *operations {
    29  	return &operations{
    30  		ops:                                     list.New(),
    31  		updateNegotiationNeededFlagOnEmptyChain: updateNegotiationNeededFlagOnEmptyChain,
    32  		onNegotiationNeeded:                     onNegotiationNeeded,
    33  	}
    34  }
    35  
    36  // Enqueue adds a new action to be executed. If there are no actions scheduled,
    37  // the execution will start immediately in a new goroutine. If the queue has been
    38  // closed, the operation will be dropped. The queue is only deliberately closed
    39  // by a user.
    40  func (o *operations) Enqueue(op operation) {
    41  	o.mu.Lock()
    42  	defer o.mu.Unlock()
    43  	_ = o.tryEnqueue(op)
    44  }
    45  
    46  // tryEnqueue attempts to enqueue the given operation. It returns false
    47  // if the op is invalid or the queue is closed. mu must be locked by
    48  // tryEnqueue's caller.
    49  func (o *operations) tryEnqueue(op operation) bool {
    50  	if op == nil {
    51  		return false
    52  	}
    53  
    54  	if o.isClosed {
    55  		return false
    56  	}
    57  	o.ops.PushBack(op)
    58  
    59  	if o.busyCh == nil {
    60  		o.busyCh = make(chan struct{})
    61  		go o.start()
    62  	}
    63  
    64  	return true
    65  }
    66  
    67  // IsEmpty checks if there are tasks in the queue
    68  func (o *operations) IsEmpty() bool {
    69  	o.mu.Lock()
    70  	defer o.mu.Unlock()
    71  	return o.ops.Len() == 0
    72  }
    73  
    74  // Done blocks until all currently enqueued operations are finished executing.
    75  // For more complex synchronization, use Enqueue directly.
    76  func (o *operations) Done() {
    77  	var wg sync.WaitGroup
    78  	wg.Add(1)
    79  	o.mu.Lock()
    80  	enqueued := o.tryEnqueue(func() {
    81  		wg.Done()
    82  	})
    83  	o.mu.Unlock()
    84  	if !enqueued {
    85  		return
    86  	}
    87  	wg.Wait()
    88  }
    89  
    90  // GracefulClose waits for the operations queue to be cleared and forbids
    91  // new operations from being enqueued.
    92  func (o *operations) GracefulClose() {
    93  	o.mu.Lock()
    94  	if o.isClosed {
    95  		o.mu.Unlock()
    96  		return
    97  	}
    98  	// do not enqueue anymore ops from here on
    99  	// o.isClosed=true will also not allow a new busyCh
   100  	// to be created.
   101  	o.isClosed = true
   102  
   103  	busyCh := o.busyCh
   104  	o.mu.Unlock()
   105  	if busyCh == nil {
   106  		return
   107  	}
   108  	<-busyCh
   109  }
   110  
   111  func (o *operations) pop() func() {
   112  	o.mu.Lock()
   113  	defer o.mu.Unlock()
   114  	if o.ops.Len() == 0 {
   115  		return nil
   116  	}
   117  
   118  	e := o.ops.Front()
   119  	o.ops.Remove(e)
   120  	if op, ok := e.Value.(operation); ok {
   121  		return op
   122  	}
   123  	return nil
   124  }
   125  
   126  func (o *operations) start() {
   127  	defer func() {
   128  		o.mu.Lock()
   129  		defer o.mu.Unlock()
   130  		// this wil lbe the most recent busy chan
   131  		close(o.busyCh)
   132  
   133  		if o.ops.Len() == 0 || o.isClosed {
   134  			o.busyCh = nil
   135  			return
   136  		}
   137  
   138  		// either a new operation was enqueued while we
   139  		// were busy, or an operation panicked
   140  		o.busyCh = make(chan struct{})
   141  		go o.start()
   142  	}()
   143  
   144  	fn := o.pop()
   145  	for fn != nil {
   146  		fn()
   147  		fn = o.pop()
   148  	}
   149  	if !o.updateNegotiationNeededFlagOnEmptyChain.get() {
   150  		return
   151  	}
   152  	o.updateNegotiationNeededFlagOnEmptyChain.set(false)
   153  	o.onNegotiationNeeded()
   154  }