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 }