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  }