github.com/dubbogo/gost@v1.14.0/container/chan/unbounded_chan.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package gxchan 19 20 import ( 21 "go.uber.org/atomic" 22 ) 23 24 import ( 25 "github.com/dubbogo/gost/container/queue" 26 ) 27 28 // UnboundedChan is a chan that could grow if the number of elements exceeds the capacity. 29 type UnboundedChan struct { 30 in chan interface{} 31 out chan interface{} 32 queue *gxqueue.CircularUnboundedQueue 33 queueLen *atomic.Int32 34 queueCap *atomic.Int32 35 } 36 37 // NewUnboundedChan creates an instance of UnboundedChan. 38 func NewUnboundedChan(capacity int) *UnboundedChan { 39 return NewUnboundedChanWithQuota(capacity, 0) 40 } 41 42 func NewUnboundedChanWithQuota(capacity, quota int) *UnboundedChan { 43 if capacity <= 0 { 44 panic("capacity should be greater than 0") 45 } 46 if quota < 0 { 47 panic("quota should be greater or equal to 0") 48 } 49 if quota != 0 && capacity > quota { 50 capacity = quota 51 } 52 53 var ( 54 incap = capacity / 3 55 outcap = capacity / 3 56 qcap = capacity - 2*(capacity/3) 57 qquota = quota - 2*(capacity/3) 58 ) 59 60 if capacity/3 > 0 { 61 incap-- 62 } else { 63 qcap-- 64 qquota-- 65 } 66 67 // address quota if the value is not valid 68 if quota == 0 { // quota == 0 means no limits for queue 69 qquota = 0 70 } else { // quota != 0 means chan couldn't grow unlimitedly 71 if qquota == 0 { 72 // qquota == 0 means queue could grow unlimitedly 73 // in this case, the total quota will be set to quota+1 74 qquota = 1 75 } 76 } 77 78 ch := &UnboundedChan{ 79 in: make(chan interface{}, incap), 80 out: make(chan interface{}, outcap), 81 queue: gxqueue.NewCircularUnboundedQueueWithQuota(qcap, qquota), 82 queueLen: &atomic.Int32{}, 83 queueCap: &atomic.Int32{}, 84 } 85 ch.queueCap.Store(int32(ch.queue.Cap())) 86 87 go ch.run() 88 89 return ch 90 } 91 92 // In returns write-only chan 93 func (ch *UnboundedChan) In() chan<- interface{} { 94 return ch.in 95 } 96 97 // Out returns read-only chan 98 func (ch *UnboundedChan) Out() <-chan interface{} { 99 return ch.out 100 } 101 102 // Len returns the total length of chan 103 func (ch *UnboundedChan) Len() int { 104 return len(ch.in) + len(ch.out) + int(ch.queueLen.Load()) 105 } 106 107 // Cap returns the total capacity of chan. 108 func (ch *UnboundedChan) Cap() int { 109 return cap(ch.in) + cap(ch.out) + int(ch.queueCap.Load()) + 1 110 } 111 112 func (ch *UnboundedChan) run() { 113 defer func() { 114 close(ch.out) 115 }() 116 117 for { 118 val, ok := <-ch.in 119 if !ok { // `ch.in` was closed and queue has no elements 120 return 121 } 122 123 select { 124 case ch.out <- val: // data was written to `ch.out` 125 continue 126 default: // `ch.out` is full, move the data to `ch.queue` 127 if ok := ch.queuePush(val); !ok { 128 ch.block(val) 129 } 130 } 131 132 for !ch.queue.IsEmpty() { 133 select { 134 case val, ok := <-ch.in: // `ch.in` was closed 135 if !ok { 136 ch.closeWait() 137 return 138 } 139 if ok = ch.queuePush(val); !ok { // try to push the value into queue 140 ch.block(val) 141 } 142 case ch.out <- ch.queue.Peek(): 143 ch.queuePop() 144 } 145 } 146 147 if ch.queue.Cap() > ch.queue.InitialCap() { 148 ch.queueReset() 149 } 150 } 151 } 152 153 // closeWait waits for being empty of `ch.queue` 154 func (ch *UnboundedChan) closeWait() { 155 for !ch.queue.IsEmpty() { 156 ch.out <- ch.queuePop() 157 } 158 } 159 160 // block waits for having an idle space on `ch.out` 161 func (ch *UnboundedChan) block(val interface{}) { 162 // `val` is not in `ch.queue` and `ch.in`, but it is stored into `UnboundedChan` 163 defer func() { 164 ch.queueLen.Add(-1) 165 }() 166 ch.queueLen.Add(1) 167 168 if !ch.queue.IsEmpty() { 169 ch.out <- ch.queue.Peek() 170 ch.queue.Pop() 171 ch.queue.Push(val) 172 return 173 } 174 175 ch.out <- val 176 } 177 178 func (ch *UnboundedChan) queuePush(val interface{}) (ok bool) { 179 ok = ch.queue.Push(val) 180 if ok { 181 ch.queueLen.Add(1) 182 ch.queueCap.Store(int32(ch.queue.Cap())) 183 } 184 return 185 } 186 187 func (ch *UnboundedChan) queueReset() { 188 ch.queue.Reset() 189 ch.queueCap.Store(int32(ch.queue.Cap())) 190 } 191 192 func (ch *UnboundedChan) queuePop() (t interface{}) { 193 t = ch.queue.Pop() 194 ch.queueLen.Add(-1) 195 return 196 }