github.com/tursom/GoCollections@v0.3.10/concurrent/collections/PublisherMessageQueue.go (about)

     1  /*
     2   * Copyright (c) 2022 tursom. All rights reserved.
     3   * Use of this source code is governed by a GPL-3
     4   * license that can be found in the LICENSE file.
     5   */
     6  
     7  package collections
     8  
     9  import (
    10  	"log"
    11  	"sync"
    12  
    13  	"github.com/tursom/GoCollections/util/time"
    14  
    15  	"github.com/tursom/GoCollections/concurrent"
    16  	"github.com/tursom/GoCollections/exceptions"
    17  	"github.com/tursom/GoCollections/lang"
    18  	"github.com/tursom/GoCollections/lang/atomic"
    19  )
    20  
    21  type (
    22  	// PublisherMessageQueue
    23  	// Enable an application to announce events to multiple interested consumers asynchronously,
    24  	// without coupling the senders to the receivers
    25  	PublisherMessageQueue[T any] struct {
    26  		end  *publisherMessageQueueNode[T]
    27  		lock sync.Mutex
    28  		cond concurrent.Cond
    29  	}
    30  
    31  	publisherMessageQueueNode[T any] struct {
    32  		index int
    33  		value T
    34  		next  *publisherMessageQueueNode[T]
    35  	}
    36  )
    37  
    38  func (q *PublisherMessageQueue[T]) getEnd() *publisherMessageQueueNode[T] {
    39  	if q.end == nil {
    40  		q.lock.Lock()
    41  		defer q.lock.Unlock()
    42  		if q.end == nil {
    43  			q.end = &publisherMessageQueueNode[T]{}
    44  		}
    45  	}
    46  	return q.end
    47  }
    48  
    49  func (q *PublisherMessageQueue[T]) getCond() concurrent.Cond {
    50  	if q.cond == nil {
    51  		q.lock.Lock()
    52  		defer q.lock.Unlock()
    53  		q.cond = concurrent.NewCond(&q.lock)
    54  	}
    55  	return q.cond
    56  }
    57  
    58  func (q *PublisherMessageQueue[T]) Subscribe() lang.ReceiveChannel[T] {
    59  	end := q.getEnd()
    60  	ch := lang.NewChannel[T](0)
    61  	canceled := false
    62  	go func() {
    63  		defer ch.Close()
    64  
    65  		cond := q.getCond()
    66  		node := &end.next
    67  		for !canceled {
    68  			// node may be nil when MQ created
    69  			for *node != nil {
    70  				if canceled {
    71  					return
    72  				}
    73  				for !ch.SendTimeout((*node).value, time.Second) && !canceled {
    74  					// check MessageQueueCapacity
    75  					if MessageQueueCapacity != -1 {
    76  						continue
    77  					}
    78  					diff := q.end.index - (*node).index
    79  					if diff >= MessageQueueWarnLimit {
    80  						log.Printf("MD is on warn stack")
    81  					}
    82  					if diff > MessageQueueCapacity {
    83  						panic(exceptions.NewIndexOutOfBound("object buffer of this MQ is full", nil))
    84  					}
    85  				}
    86  				node = &(*node).next
    87  			}
    88  			cond.Wait()
    89  		}
    90  	}()
    91  	return lang.WithReceiveChannel[T](ch, func() {
    92  		canceled = true
    93  	})
    94  }
    95  
    96  func (q *PublisherMessageQueue[T]) Send(msg T) {
    97  	index := 0
    98  	if q.end != nil {
    99  		index = q.end.index + 1
   100  	}
   101  
   102  	node := &publisherMessageQueueNode[T]{
   103  		index: index,
   104  		value: msg,
   105  	}
   106  
   107  	p := &q.getEnd().next
   108  	for !atomic.CompareAndSwapPointer(p, nil, node) {
   109  		for *p != nil {
   110  			p = &q.end.next
   111  		}
   112  		node.index = q.end.index + 1
   113  	}
   114  	q.end = node
   115  	q.getCond().Broadcast()
   116  }