implemented event and req
This commit is contained in:
60
pkg/utils/qu/README.adoc
Normal file
60
pkg/utils/qu/README.adoc
Normal file
@@ -0,0 +1,60 @@
|
||||
= qu
|
||||
|
||||
===== observable signal channels
|
||||
|
||||
simple channels that act as breakers or momentary one-shot triggers.
|
||||
|
||||
can enable logging to get detailed information on channel state, and channels do
|
||||
not panic if closed channels are attempted to be closed or signalled with.
|
||||
|
||||
provides a neat function based syntax for usage.
|
||||
|
||||
wait function does require use of the `<-` receive operator prefix to be used in
|
||||
a select statement.
|
||||
|
||||
== usage
|
||||
|
||||
=== creating channels:
|
||||
|
||||
==== unbuffered
|
||||
|
||||
----
|
||||
newSigChan := qu.T()
|
||||
----
|
||||
|
||||
==== buffered
|
||||
|
||||
----
|
||||
newBufferedSigChan := qu.Ts(5)
|
||||
----
|
||||
|
||||
==== closing
|
||||
|
||||
----
|
||||
newSigChan.Q()
|
||||
----
|
||||
|
||||
==== signalling
|
||||
|
||||
----
|
||||
newBufferedSigChan.Signal()
|
||||
----
|
||||
|
||||
==== logging features
|
||||
|
||||
----
|
||||
numberOpenUnbufferedChannels := GetOpenUnbufferedChanCount()
|
||||
|
||||
numberOpenBufferedChannels := GetOpenBufferedChanCount()
|
||||
----
|
||||
|
||||
print a list of closed and open channels known by qu:
|
||||
|
||||
----
|
||||
PrintChanState()
|
||||
----
|
||||
|
||||
== garbage collection
|
||||
|
||||
this library automatically cleans up closed channels once a minute to free
|
||||
resources that have become unused.
|
||||
245
pkg/utils/qu/qu.go
Normal file
245
pkg/utils/qu/qu.go
Normal file
@@ -0,0 +1,245 @@
|
||||
// Package qu is a library for making handling signal (chan struct{}) channels
|
||||
// simpler, as well as monitoring the state of the signal channels in an
|
||||
// application.
|
||||
package qu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"lol.mleku.dev"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// C is your basic empty struct signal channel
|
||||
type C chan struct{}
|
||||
|
||||
var (
|
||||
createdList []string
|
||||
createdChannels []C
|
||||
createdChannelBufferCounts []int
|
||||
mx sync.Mutex
|
||||
logEnabled = atomic.NewBool(false)
|
||||
)
|
||||
|
||||
// SetLogging switches on and off the channel logging
|
||||
func SetLogging(on bool) {
|
||||
logEnabled.Store(on)
|
||||
}
|
||||
|
||||
func l(a ...interface{}) {
|
||||
if logEnabled.Load() {
|
||||
log.D.Ln(a...)
|
||||
}
|
||||
}
|
||||
|
||||
func lc(cl func() string) {
|
||||
if logEnabled.Load() {
|
||||
log.D.Ln(cl())
|
||||
}
|
||||
}
|
||||
|
||||
// T creates an unbuffered chan struct{} for trigger and quit signalling
|
||||
// (momentary and breaker switches)
|
||||
func T() C {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
msg := fmt.Sprintf("chan from %s", lol.GetLoc(1))
|
||||
l("created", msg)
|
||||
createdList = append(createdList, msg)
|
||||
o := make(C)
|
||||
createdChannels = append(createdChannels, o)
|
||||
createdChannelBufferCounts = append(createdChannelBufferCounts, 0)
|
||||
return o
|
||||
}
|
||||
|
||||
// Ts creates a buffered chan struct{} which is specifically intended for
|
||||
// signalling without blocking, generally one is the size of buffer to be used,
|
||||
// though there might be conceivable cases where the channel should accept more
|
||||
// signals without blocking the caller
|
||||
func Ts(n int) C {
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
msg := fmt.Sprintf("buffered chan (%d) from %s", n, lol.GetLoc(1))
|
||||
l("created", msg)
|
||||
createdList = append(createdList, msg)
|
||||
o := make(C, n)
|
||||
createdChannels = append(createdChannels, o)
|
||||
createdChannelBufferCounts = append(createdChannelBufferCounts, n)
|
||||
return o
|
||||
}
|
||||
|
||||
// Q closes the channel, which makes it emit a nil every time it is selected.
|
||||
func (c C) Q() {
|
||||
open := !testChanIsClosed(c)
|
||||
lc(
|
||||
func() (o string) {
|
||||
lo := getLocForChan(c)
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
if open {
|
||||
return "closing chan from " + lo + "\n" + strings.Repeat(
|
||||
" ",
|
||||
48,
|
||||
) + "from" + lol.GetLoc(1)
|
||||
} else {
|
||||
return "from" + lol.GetLoc(1) + "\n" + strings.Repeat(" ", 48) +
|
||||
"channel " + lo + " was already closed"
|
||||
}
|
||||
},
|
||||
)
|
||||
if open {
|
||||
close(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Signal sends struct{}{} on the channel which functions as a momentary switch,
|
||||
// useful in pairs for stop/start
|
||||
func (c C) Signal() {
|
||||
lc(func() (o string) { return "signalling " + getLocForChan(c) })
|
||||
if !testChanIsClosed(c) {
|
||||
c <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait should be placed with a `<-` in a select case in addition to the channel
|
||||
// variable name
|
||||
func (c C) Wait() <-chan struct{} {
|
||||
lc(
|
||||
func() (o string) {
|
||||
return fmt.Sprint(
|
||||
"waiting on "+getLocForChan(c)+"at",
|
||||
lol.GetLoc(1),
|
||||
)
|
||||
},
|
||||
)
|
||||
return c
|
||||
}
|
||||
|
||||
// IsClosed exposes a test to see if the channel is closed
|
||||
func (c C) IsClosed() bool {
|
||||
return testChanIsClosed(c)
|
||||
}
|
||||
|
||||
// testChanIsClosed allows you to see whether the channel has been closed so you
|
||||
// can avoid a panic by trying to close or signal on it
|
||||
func testChanIsClosed(ch C) (o bool) {
|
||||
if ch == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-ch:
|
||||
o = true
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getLocForChan finds which record connects to the channel in question
|
||||
func getLocForChan(c C) (s string) {
|
||||
s = "not found"
|
||||
mx.Lock()
|
||||
for i := range createdList {
|
||||
if i >= len(createdChannels) {
|
||||
break
|
||||
}
|
||||
if createdChannels[i] == c {
|
||||
s = createdList[i]
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// once a minute clean up the channel cache to remove closed channels no longer
|
||||
// in use
|
||||
func init() {
|
||||
go func() {
|
||||
for {
|
||||
<-time.After(time.Minute)
|
||||
l("cleaning up closed channels")
|
||||
var c []C
|
||||
var ll []string
|
||||
mx.Lock()
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdList) {
|
||||
break
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
} else {
|
||||
c = append(c, createdChannels[i])
|
||||
ll = append(ll, createdList[i])
|
||||
}
|
||||
}
|
||||
createdChannels = c
|
||||
createdList = ll
|
||||
mx.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// PrintChanState creates an output showing the current state of the channels
|
||||
// being monitored This is a function for use by the programmer while debugging
|
||||
func PrintChanState() {
|
||||
mx.Lock()
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdList) {
|
||||
break
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
log.T.Ln(">>> closed", createdList[i])
|
||||
} else {
|
||||
log.T.Ln("<<< open", createdList[i])
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
}
|
||||
|
||||
// GetOpenUnbufferedChanCount returns the number of qu channels that are still
|
||||
// open
|
||||
func GetOpenUnbufferedChanCount() (o int) {
|
||||
mx.Lock()
|
||||
var c int
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdChannels) {
|
||||
break
|
||||
}
|
||||
// skip buffered channels
|
||||
if createdChannelBufferCounts[i] > 0 {
|
||||
continue
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
c++
|
||||
} else {
|
||||
o++
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// GetOpenBufferedChanCount returns the number of qu channels that are still
|
||||
// open
|
||||
func GetOpenBufferedChanCount() (o int) {
|
||||
mx.Lock()
|
||||
var c int
|
||||
for i := range createdChannels {
|
||||
if i >= len(createdChannels) {
|
||||
break
|
||||
}
|
||||
// skip unbuffered channels
|
||||
if createdChannelBufferCounts[i] < 1 {
|
||||
continue
|
||||
}
|
||||
if testChanIsClosed(createdChannels[i]) {
|
||||
c++
|
||||
} else {
|
||||
o++
|
||||
}
|
||||
}
|
||||
mx.Unlock()
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user