You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
5.2 KiB
245 lines
5.2 KiB
// 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 |
|
}
|
|
|