2024-10-16 01:29:44 +09:00
|
|
|
// Package fmsg provides various functions for output messages.
|
|
|
|
package fmsg
|
2024-10-21 20:47:02 +09:00
|
|
|
|
|
|
|
import (
|
2025-02-16 17:26:09 +09:00
|
|
|
"bytes"
|
|
|
|
"io"
|
2024-10-21 20:47:02 +09:00
|
|
|
"log"
|
|
|
|
"os"
|
2025-02-16 17:26:09 +09:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"syscall"
|
2024-10-21 20:47:02 +09:00
|
|
|
)
|
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
const (
|
|
|
|
bufSize = 4 * 1024
|
|
|
|
bufSizeMax = 16 * 1024 * 1024
|
|
|
|
)
|
2024-10-21 20:47:02 +09:00
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
var o = &suspendable{w: os.Stderr}
|
|
|
|
|
|
|
|
// Prepare configures the system logger for [Suspend] and [Resume] to take effect.
|
|
|
|
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
|
|
|
|
|
|
|
|
type suspendable struct {
|
|
|
|
w io.Writer
|
|
|
|
s atomic.Bool
|
2024-10-21 20:47:02 +09:00
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
buf bytes.Buffer
|
|
|
|
bufOnce sync.Once
|
|
|
|
bufMu sync.Mutex
|
|
|
|
dropped int
|
2024-10-21 20:47:02 +09:00
|
|
|
}
|
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
func (s *suspendable) Write(p []byte) (n int, err error) {
|
|
|
|
if !s.s.Load() {
|
|
|
|
return s.w.Write(p)
|
|
|
|
}
|
|
|
|
s.bufOnce.Do(func() { s.prepareBuf() })
|
|
|
|
|
|
|
|
s.bufMu.Lock()
|
|
|
|
defer s.bufMu.Unlock()
|
|
|
|
|
|
|
|
if l := len(p); s.buf.Len()+l > bufSizeMax {
|
|
|
|
s.dropped += l
|
|
|
|
return 0, syscall.ENOMEM
|
|
|
|
}
|
|
|
|
return s.buf.Write(p)
|
2024-10-21 20:47:02 +09:00
|
|
|
}
|
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
|
|
|
|
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
|
|
|
|
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
|
|
|
|
if o.s.CompareAndSwap(true, false) {
|
|
|
|
o.bufMu.Lock()
|
|
|
|
defer o.bufMu.Unlock()
|
|
|
|
|
|
|
|
resumed = true
|
|
|
|
dropped = uintptr(o.dropped)
|
|
|
|
|
|
|
|
o.dropped = 0
|
|
|
|
n, err = io.Copy(s.w, &s.buf)
|
|
|
|
s.buf = bytes.Buffer{}
|
|
|
|
s.prepareBuf()
|
|
|
|
}
|
|
|
|
return
|
2024-10-21 20:47:02 +09:00
|
|
|
}
|
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
func Suspend() bool { return o.Suspend() }
|
|
|
|
func Resume() bool {
|
|
|
|
resumed, dropped, _, err := o.Resume()
|
|
|
|
if err != nil {
|
|
|
|
// probably going to result in an error as well,
|
|
|
|
// so this call is as good as unreachable
|
|
|
|
log.Printf("cannot dump buffer on resume: %v", err)
|
|
|
|
}
|
|
|
|
if resumed && dropped > 0 {
|
|
|
|
log.Fatalf("dropped %d bytes while output is suspended", dropped)
|
|
|
|
}
|
|
|
|
return resumed
|
2024-10-21 20:47:02 +09:00
|
|
|
}
|
|
|
|
|
2025-02-16 17:26:09 +09:00
|
|
|
func BeforeExit() {
|
|
|
|
if Resume() {
|
|
|
|
log.Printf("beforeExit reached on suspended output")
|
|
|
|
}
|
2024-10-21 20:47:02 +09:00
|
|
|
}
|