Compare commits

...

3 Commits

Author SHA1 Message Date
7e6adf9fba Adjust BadgerDB memory configurations to optimize resource usage and bump version to v0.8.9.
Some checks failed
Go / build (push) Has been cancelled
2025-10-01 12:52:45 +01:00
7d5ebd5ccd Adjust BadgerDB memory settings to prevent OOM issues and update version to v0.8.8. 2025-10-01 12:40:34 +01:00
f8a321eaee Add connection start time tracking and improve logging for WebSocket lifecycle
Some checks failed
Go / build (push) Has been cancelled
- Introduced `startTime` field in `Listener` to log connection duration.
- Enhanced diagnostics with detailed connection statistics on close.
- Improved logging and error handling for PING/PONG and message lifecycle.
- Updated version to v0.8.7.
2025-10-01 08:40:04 +01:00
5 changed files with 60 additions and 30 deletions

View File

@@ -65,18 +65,17 @@ whitelist:
conn.SetReadLimit(DefaultMaxMessageSize)
defer conn.CloseNow()
listener := &Listener{
ctx: ctx,
Server: s,
conn: conn,
remote: remote,
req: r,
ctx: ctx,
Server: s,
conn: conn,
remote: remote,
req: r,
startTime: time.Now(),
}
chal := make([]byte, 32)
rand.Read(chal)
listener.challenge.Store([]byte(hex.Enc(chal)))
// If admins are configured, immediately prompt client to AUTH (NIP-42)
if len(s.Config.Admins) > 0 {
// log.D.F("sending initial AUTH challenge to %s", remote)
if s.Config.ACLMode != "none" {
log.D.F("sending AUTH challenge to %s", remote)
if err = authenvelope.NewChallengeWith(listener.challenge.Load()).
Write(listener); chk.E(err) {
@@ -89,20 +88,23 @@ whitelist:
go s.Pinger(ctx, conn, ticker, cancel)
defer func() {
log.D.F("closing websocket connection from %s", remote)
// Cancel context and stop pinger
cancel()
ticker.Stop()
// Cancel all subscriptions for this connection
log.D.F("cancelling subscriptions for %s", remote)
listener.publishers.Receive(&W{Cancel: true})
// Log detailed connection statistics
log.D.F("ws connection closed %s: msgs=%d, REQs=%d, EVENTs=%d, duration=%v",
remote, listener.msgCount, listener.reqCount, listener.eventCount,
time.Since(time.Now())) // Note: This will be near-zero, would need start time tracked
dur := time.Since(listener.startTime)
log.D.F(
"ws connection closed %s: msgs=%d, REQs=%d, EVENTs=%d, duration=%v",
remote, listener.msgCount, listener.reqCount, listener.eventCount,
dur,
)
// Log any remaining connection state
if listener.authedPubkey.Load() != nil {
log.D.F("ws connection %s was authenticated", remote)
@@ -118,7 +120,7 @@ whitelist:
}
var typ websocket.MessageType
var msg []byte
// log.T.F("waiting for message from %s", remote)
log.T.F("waiting for message from %s", remote)
// Block waiting for message; rely on pings and context cancellation to detect dead peers
typ, msg, err = conn.Read(ctx)
@@ -160,9 +162,15 @@ whitelist:
pongStart := time.Now()
if err = conn.Write(writeCtx, PongMessage, msg); chk.E(err) {
pongDuration := time.Since(pongStart)
log.E.F("failed to send PONG to %s after %v: %v", remote, pongDuration, err)
log.E.F(
"failed to send PONG to %s after %v: %v", remote,
pongDuration, err,
)
if writeCtx.Err() != nil {
log.E.F("PONG write timeout to %s after %v (limit=%v)", remote, pongDuration, DefaultWriteTimeout)
log.E.F(
"PONG write timeout to %s after %v (limit=%v)", remote,
pongDuration, DefaultWriteTimeout,
)
}
writeCancel()
return
@@ -196,31 +204,37 @@ func (s *Server) Pinger(
case <-ticker.C:
pingCount++
log.D.F("sending PING #%d", pingCount)
// Create a write context with timeout for ping operation
pingCtx, pingCancel := context.WithTimeout(ctx, DefaultWriteTimeout)
pingStart := time.Now()
if err = conn.Ping(pingCtx); err != nil {
pingDuration := time.Since(pingStart)
log.E.F("PING #%d FAILED after %v: %v", pingCount, pingDuration, err)
log.E.F(
"PING #%d FAILED after %v: %v", pingCount, pingDuration,
err,
)
if pingCtx.Err() != nil {
log.E.F("PING #%d timeout after %v (limit=%v)", pingCount, pingDuration, DefaultWriteTimeout)
log.E.F(
"PING #%d timeout after %v (limit=%v)", pingCount,
pingDuration, DefaultWriteTimeout,
)
}
chk.E(err)
pingCancel()
return
}
pingDuration := time.Since(pingStart)
log.D.F("PING #%d sent successfully in %v", pingCount, pingDuration)
if pingDuration > time.Millisecond*100 {
log.D.F("SLOW PING #%d: %v (>100ms)", pingCount, pingDuration)
}
pingCancel()
case <-ctx.Done():
log.D.F("pinger context cancelled after %d pings", pingCount)

View File

@@ -19,6 +19,7 @@ type Listener struct {
req *http.Request
challenge atomic.Bytes
authedPubkey atomic.Bytes
startTime time.Time
// Diagnostics: per-connection counters
msgCount int
reqCount int

View File

@@ -52,8 +52,18 @@ func New(
}
opts := badger.DefaultOptions(d.dataDir)
opts.BlockCacheSize = int64(units.Gb)
opts.BlockSize = units.Gb
// Use sane defaults to avoid excessive memory usage during startup.
// Badger's default BlockSize is small (e.g., 4KB). Overriding it to very large values
// can cause massive allocations and OOM panics during deployments.
// Set BlockCacheSize to a moderate value and keep BlockSize small.
opts.BlockCacheSize = int64(256 * units.Mb) // 256 MB cache
opts.BlockSize = 4 * units.Kb // 4 KB block size
// Prevent huge allocations during table building and memtable flush.
// Badger's TableBuilder buffer is sized by BaseTableSize; ensure it's small.
opts.BaseTableSize = 64 * units.Mb // 64 MB per table (default ~2MB, increased for fewer files but safe)
opts.MemTableSize = 64 * units.Mb // 64 MB memtable to match table size
// Keep value log files to a moderate size as well
opts.ValueLogFileSize = 256 * units.Mb // 256 MB value log files
opts.CompactL0OnClose = true
opts.LmaxCompaction = true
opts.Compression = options.None

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"lol.mleku.dev/chk"
"lol.mleku.dev/log"
"next.orly.dev/pkg/utils"
)
@@ -83,6 +84,10 @@ func (s *S) MarshalJSON() (b []byte, err error) {
}
func (s *S) Marshal(dst []byte) (b []byte) {
if s == nil {
log.I.F("tags cannot be used without initialization")
return
}
b = dst
b = append(b, '[')
for i, ss := range *s {

View File

@@ -1 +1 @@
v0.8.6
v0.8.9