v0.1.4 fixed blocking shutdown, better isolation of the streams
This commit is contained in:
23
README.md
23
README.md
@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="assets/logo.svg" alt="LogWisp Logo" width="200"/>
|
<img src="assets/logwisp-logo.svg" alt="LogWisp Logo" width="200"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# LogWisp - Dual-Stack Log Streaming
|
# LogWisp - Dual-Stack Log Streaming
|
||||||
@ -56,10 +56,6 @@ OPTIONS:
|
|||||||
--http-port PORT HTTP port (default: 8080)
|
--http-port PORT HTTP port (default: 8080)
|
||||||
--http-buffer-size SIZE HTTP buffer size (default: 1000)
|
--http-buffer-size SIZE HTTP buffer size (default: 1000)
|
||||||
|
|
||||||
# Legacy compatibility
|
|
||||||
--port PORT Same as --http-port
|
|
||||||
--buffer-size SIZE Same as --http-buffer-size
|
|
||||||
|
|
||||||
TARGET:
|
TARGET:
|
||||||
path[:pattern[:isfile]] Path to monitor
|
path[:pattern[:isfile]] Path to monitor
|
||||||
pattern: glob pattern for directories
|
pattern: glob pattern for directories
|
||||||
@ -168,6 +164,8 @@ LogWisp supports configurable heartbeat messages for both HTTP/SSE and TCP strea
|
|||||||
- Same content options as HTTP
|
- Same content options as HTTP
|
||||||
- Useful for detecting disconnected clients
|
- Useful for detecting disconnected clients
|
||||||
|
|
||||||
|
**⚠️ SECURITY:** Heartbeat statistics expose minimal server state (connection count, uptime). If this is sensitive in your environment, disable `include_stats`.
|
||||||
|
|
||||||
**Example Heartbeat Messages:**
|
**Example Heartbeat Messages:**
|
||||||
|
|
||||||
HTTP Comment format:
|
HTTP Comment format:
|
||||||
@ -196,21 +194,6 @@ format = "json"
|
|||||||
- `LOGWISP_TCPSERVER_HEARTBEAT_ENABLED`
|
- `LOGWISP_TCPSERVER_HEARTBEAT_ENABLED`
|
||||||
- `LOGWISP_TCPSERVER_HEARTBEAT_INTERVAL_SECONDS`
|
- `LOGWISP_TCPSERVER_HEARTBEAT_INTERVAL_SECONDS`
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
**Fixed:**
|
|
||||||
- Removed duplicate `globToRegex` functions (never used)
|
|
||||||
- Added missing TCP heartbeat support
|
|
||||||
- Made HTTP heartbeat configurable
|
|
||||||
|
|
||||||
**Enhanced:**
|
|
||||||
- Configurable heartbeat interval
|
|
||||||
- Multiple format options (comment/JSON)
|
|
||||||
- Optional timestamp and statistics
|
|
||||||
- Per-protocol configuration
|
|
||||||
|
|
||||||
**⚠️ SECURITY:** Heartbeat statistics expose minimal server state (connection count, uptime). If this is sensitive in your environment, disable `include_stats`.
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Systemd Service
|
### Systemd Service
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -20,10 +20,7 @@ func main() {
|
|||||||
// Parse CLI flags
|
// Parse CLI flags
|
||||||
var (
|
var (
|
||||||
configFile = flag.String("config", "", "Config file path")
|
configFile = flag.String("config", "", "Config file path")
|
||||||
// Legacy compatibility flags
|
// Flags
|
||||||
port = flag.Int("port", 0, "HTTP port (legacy, maps to --http-port)")
|
|
||||||
bufferSize = flag.Int("buffer-size", 0, "Buffer size (legacy, maps to --http-buffer-size)")
|
|
||||||
// New explicit flags
|
|
||||||
httpPort = flag.Int("http-port", 0, "HTTP server port")
|
httpPort = flag.Int("http-port", 0, "HTTP server port")
|
||||||
httpBuffer = flag.Int("http-buffer-size", 0, "HTTP server buffer size")
|
httpBuffer = flag.Int("http-buffer-size", 0, "HTTP server buffer size")
|
||||||
tcpPort = flag.Int("tcp-port", 0, "TCP server port")
|
tcpPort = flag.Int("tcp-port", 0, "TCP server port")
|
||||||
@ -41,15 +38,7 @@ func main() {
|
|||||||
// Build CLI args for config
|
// Build CLI args for config
|
||||||
var cliArgs []string
|
var cliArgs []string
|
||||||
|
|
||||||
// Legacy mapping
|
// Flags
|
||||||
if *port > 0 {
|
|
||||||
cliArgs = append(cliArgs, fmt.Sprintf("--httpserver.port=%d", *port))
|
|
||||||
}
|
|
||||||
if *bufferSize > 0 {
|
|
||||||
cliArgs = append(cliArgs, fmt.Sprintf("--httpserver.buffer_size=%d", *bufferSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// New flags
|
|
||||||
if *httpPort > 0 {
|
if *httpPort > 0 {
|
||||||
cliArgs = append(cliArgs, fmt.Sprintf("--httpserver.port=%d", *httpPort))
|
cliArgs = append(cliArgs, fmt.Sprintf("--httpserver.port=%d", *httpPort))
|
||||||
}
|
}
|
||||||
@ -92,8 +81,6 @@ func main() {
|
|||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Create monitor
|
// Create monitor
|
||||||
mon := monitor.New()
|
mon := monitor.New()
|
||||||
mon.SetCheckInterval(time.Duration(cfg.Monitor.CheckIntervalMs) * time.Millisecond)
|
mon.SetCheckInterval(time.Duration(cfg.Monitor.CheckIntervalMs) * time.Millisecond)
|
||||||
@ -119,14 +106,23 @@ func main() {
|
|||||||
tcpChan := mon.Subscribe()
|
tcpChan := mon.Subscribe()
|
||||||
tcpServer = stream.NewTCPStreamer(tcpChan, cfg.TCPServer)
|
tcpServer = stream.NewTCPStreamer(tcpChan, cfg.TCPServer)
|
||||||
|
|
||||||
wg.Add(1)
|
// Start TCP server in separate goroutine without blocking wg.Wait()
|
||||||
|
tcpStarted := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
tcpStarted <- tcpServer.Start()
|
||||||
if err := tcpServer.Start(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "TCP server error: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check if TCP server started successfully
|
||||||
|
select {
|
||||||
|
case err := <-tcpStarted:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "TCP server failed to start: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
// Server is running
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("TCP streaming on port %d\n", cfg.TCPServer.Port)
|
fmt.Printf("TCP streaming on port %d\n", cfg.TCPServer.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,14 +131,23 @@ func main() {
|
|||||||
httpChan := mon.Subscribe()
|
httpChan := mon.Subscribe()
|
||||||
httpServer = stream.NewHTTPStreamer(httpChan, cfg.HTTPServer)
|
httpServer = stream.NewHTTPStreamer(httpChan, cfg.HTTPServer)
|
||||||
|
|
||||||
wg.Add(1)
|
// Start HTTP server in separate goroutine without blocking wg.Wait()
|
||||||
|
httpStarted := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
httpStarted <- httpServer.Start()
|
||||||
if err := httpServer.Start(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "HTTP server error: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Check if HTTP server started successfully
|
||||||
|
select {
|
||||||
|
case err := <-httpStarted:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "HTTP server failed to start: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
// Server is running
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("HTTP/SSE streaming on http://localhost:%d/stream\n", cfg.HTTPServer.Port)
|
fmt.Printf("HTTP/SSE streaming on http://localhost:%d/stream\n", cfg.HTTPServer.Port)
|
||||||
fmt.Printf("Status available at http://localhost:%d/status\n", cfg.HTTPServer.Port)
|
fmt.Printf("Status available at http://localhost:%d/status\n", cfg.HTTPServer.Port)
|
||||||
}
|
}
|
||||||
@ -156,29 +161,67 @@ func main() {
|
|||||||
<-sigChan
|
<-sigChan
|
||||||
fmt.Println("\nShutting down...")
|
fmt.Println("\nShutting down...")
|
||||||
|
|
||||||
// Stop servers first
|
// Create shutdown group for concurrent server stops
|
||||||
|
var shutdownWg sync.WaitGroup
|
||||||
|
|
||||||
|
// Stop servers first (concurrently)
|
||||||
if tcpServer != nil {
|
if tcpServer != nil {
|
||||||
|
shutdownWg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer shutdownWg.Done()
|
||||||
tcpServer.Stop()
|
tcpServer.Stop()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
if httpServer != nil {
|
if httpServer != nil {
|
||||||
|
shutdownWg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer shutdownWg.Done()
|
||||||
httpServer.Stop()
|
httpServer.Stop()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel context and stop monitor
|
// Cancel context to stop monitor
|
||||||
cancel()
|
cancel()
|
||||||
mon.Stop()
|
|
||||||
|
|
||||||
// Wait for completion
|
// Wait for servers to stop with timeout
|
||||||
done := make(chan struct{})
|
serversDone := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
shutdownWg.Wait()
|
||||||
close(done)
|
close(serversDone)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Stop monitor after context cancellation
|
||||||
|
monitorDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
mon.Stop()
|
||||||
|
close(monitorDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for all components with proper timeout
|
||||||
|
shutdownTimeout := 5 * time.Second
|
||||||
|
shutdownTimer := time.NewTimer(shutdownTimeout)
|
||||||
|
defer shutdownTimer.Stop()
|
||||||
|
|
||||||
|
serversShutdown := false
|
||||||
|
monitorShutdown := false
|
||||||
|
|
||||||
|
for !serversShutdown || !monitorShutdown {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-serversDone:
|
||||||
fmt.Println("Shutdown complete")
|
serversShutdown = true
|
||||||
case <-time.After(2 * time.Second):
|
case <-monitorDone:
|
||||||
fmt.Println("Shutdown timeout")
|
monitorShutdown = true
|
||||||
|
case <-shutdownTimer.C:
|
||||||
|
if !serversShutdown {
|
||||||
|
fmt.Println("Warning: Server shutdown timeout")
|
||||||
}
|
}
|
||||||
|
if !monitorShutdown {
|
||||||
|
fmt.Println("Warning: Monitor shutdown timeout")
|
||||||
|
}
|
||||||
|
fmt.Println("Forcing exit")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Shutdown complete")
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package stream
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -22,6 +23,8 @@ type HTTPStreamer struct {
|
|||||||
activeClients atomic.Int32
|
activeClients atomic.Int32
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
|
done chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPStreamer(logChan chan monitor.LogEntry, cfg config.HTTPConfig) *HTTPStreamer {
|
func NewHTTPStreamer(logChan chan monitor.LogEntry, cfg config.HTTPConfig) *HTTPStreamer {
|
||||||
@ -29,6 +32,7 @@ func NewHTTPStreamer(logChan chan monitor.LogEntry, cfg config.HTTPConfig) *HTTP
|
|||||||
logChan: logChan,
|
logChan: logChan,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,13 +45,42 @@ func (h *HTTPStreamer) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf(":%d", h.config.Port)
|
addr := fmt.Sprintf(":%d", h.config.Port)
|
||||||
return h.server.ListenAndServe(addr)
|
|
||||||
|
// Run server in separate goroutine to avoid blocking
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
err := h.server.ListenAndServe(addr)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check if server started successfully
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return err
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
// Server started successfully
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPStreamer) Stop() {
|
func (h *HTTPStreamer) Stop() {
|
||||||
|
// Signal all client handlers to stop
|
||||||
|
close(h.done)
|
||||||
|
|
||||||
|
// Shutdown HTTP server
|
||||||
if h.server != nil {
|
if h.server != nil {
|
||||||
h.server.Shutdown()
|
// Create context with timeout for server shutdown
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Use ShutdownWithContext for graceful shutdown
|
||||||
|
h.server.ShutdownWithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for all active client handlers to finish
|
||||||
|
h.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPStreamer) requestHandler(ctx *fasthttp.RequestCtx) {
|
func (h *HTTPStreamer) requestHandler(ctx *fasthttp.RequestCtx) {
|
||||||
@ -72,25 +105,46 @@ func (h *HTTPStreamer) handleStream(ctx *fasthttp.RequestCtx) {
|
|||||||
ctx.Response.Header.Set("X-Accel-Buffering", "no")
|
ctx.Response.Header.Set("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
h.activeClients.Add(1)
|
h.activeClients.Add(1)
|
||||||
defer h.activeClients.Add(-1)
|
h.wg.Add(1) // Track this client handler
|
||||||
|
defer func() {
|
||||||
|
h.activeClients.Add(-1)
|
||||||
|
h.wg.Done() // Mark handler as done
|
||||||
|
}()
|
||||||
|
|
||||||
// Create subscription for this client
|
// Create subscription for this client
|
||||||
clientChan := make(chan monitor.LogEntry, h.config.BufferSize)
|
clientChan := make(chan monitor.LogEntry, h.config.BufferSize)
|
||||||
|
clientDone := make(chan struct{})
|
||||||
|
|
||||||
// Subscribe to monitor's broadcast
|
// Subscribe to monitor's broadcast
|
||||||
go func() {
|
go func() {
|
||||||
for entry := range h.logChan {
|
defer close(clientChan)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry, ok := <-h.logChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case clientChan <- entry:
|
case clientChan <- entry:
|
||||||
|
case <-clientDone:
|
||||||
|
return
|
||||||
|
case <-h.done: // Check for server shutdown
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
// Drop if client buffer full
|
// Drop if client buffer full
|
||||||
}
|
}
|
||||||
|
case <-clientDone:
|
||||||
|
return
|
||||||
|
case <-h.done: // Check for server shutdown
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
close(clientChan)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Define the stream writer function
|
// Define the stream writer function
|
||||||
streamFunc := func(w *bufio.Writer) {
|
streamFunc := func(w *bufio.Writer) {
|
||||||
|
defer close(clientDone)
|
||||||
|
|
||||||
// Send initial connected event
|
// Send initial connected event
|
||||||
clientID := fmt.Sprintf("%d", time.Now().UnixNano())
|
clientID := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
fmt.Fprintf(w, "event: connected\ndata: {\"client_id\":\"%s\"}\n\n", clientID)
|
fmt.Fprintf(w, "event: connected\ndata: {\"client_id\":\"%s\"}\n\n", clientID)
|
||||||
@ -129,6 +183,12 @@ func (h *HTTPStreamer) handleStream(ctx *fasthttp.RequestCtx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case <-h.done: // ADDED: Check for server shutdown
|
||||||
|
// Send final disconnect event
|
||||||
|
fmt.Fprintf(w, "event: disconnect\ndata: {\"reason\":\"server_shutdown\"}\n\n")
|
||||||
|
w.Flush()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
@ -17,9 +18,11 @@ type TCPStreamer struct {
|
|||||||
logChan chan monitor.LogEntry
|
logChan chan monitor.LogEntry
|
||||||
config config.TCPConfig
|
config config.TCPConfig
|
||||||
server *tcpServer
|
server *tcpServer
|
||||||
done chan struct{}
|
|
||||||
activeConns atomic.Int32
|
activeConns atomic.Int32
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
|
done chan struct{}
|
||||||
|
engine *gnet.Engine
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpServer struct {
|
type tcpServer struct {
|
||||||
@ -32,8 +35,8 @@ func NewTCPStreamer(logChan chan monitor.LogEntry, cfg config.TCPConfig) *TCPStr
|
|||||||
return &TCPStreamer{
|
return &TCPStreamer{
|
||||||
logChan: logChan,
|
logChan: logChan,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
done: make(chan struct{}),
|
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,23 +44,53 @@ func (t *TCPStreamer) Start() error {
|
|||||||
t.server = &tcpServer{streamer: t}
|
t.server = &tcpServer{streamer: t}
|
||||||
|
|
||||||
// Start log broadcast loop
|
// Start log broadcast loop
|
||||||
go t.broadcastLoop()
|
t.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer t.wg.Done()
|
||||||
|
t.broadcastLoop()
|
||||||
|
}()
|
||||||
|
|
||||||
// Configure gnet with no-op logger
|
// Configure gnet with no-op logger
|
||||||
addr := fmt.Sprintf("tcp://:%d", t.config.Port)
|
addr := fmt.Sprintf("tcp://:%d", t.config.Port)
|
||||||
|
|
||||||
|
// Run gnet in separate goroutine to avoid blocking
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
err := gnet.Run(t.server, addr,
|
err := gnet.Run(t.server, addr,
|
||||||
gnet.WithLogger(noopLogger{}), // No-op logger: discard everything
|
gnet.WithLogger(noopLogger{}), // No-op logger: discard everything
|
||||||
gnet.WithMulticore(true),
|
gnet.WithMulticore(true),
|
||||||
gnet.WithReusePort(true),
|
gnet.WithReusePort(true),
|
||||||
)
|
)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait briefly for server to start or fail
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
// Server failed immediately
|
||||||
|
close(t.done)
|
||||||
|
t.wg.Wait()
|
||||||
return err
|
return err
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
// Server started successfully
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPStreamer) Stop() {
|
func (t *TCPStreamer) Stop() {
|
||||||
|
// Signal broadcast loop to stop
|
||||||
close(t.done)
|
close(t.done)
|
||||||
// No engine to stop with gnet v2
|
|
||||||
|
// Stop gnet engine if running
|
||||||
|
if t.engine != nil {
|
||||||
|
// Use Stop() method to gracefully shutdown gnet
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
t.engine.Stop(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for broadcast loop to finish
|
||||||
|
t.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPStreamer) broadcastLoop() {
|
func (t *TCPStreamer) broadcastLoop() {
|
||||||
@ -72,7 +105,10 @@ func (t *TCPStreamer) broadcastLoop() {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case entry := <-t.logChan:
|
case entry, ok := <-t.logChan:
|
||||||
|
if !ok {
|
||||||
|
return // Channel closed
|
||||||
|
}
|
||||||
data, err := json.Marshal(entry)
|
data, err := json.Marshal(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@ -122,6 +158,7 @@ func (t *TCPStreamer) formatHeartbeat() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *tcpServer) OnBoot(eng gnet.Engine) gnet.Action {
|
func (s *tcpServer) OnBoot(eng gnet.Engine) gnet.Action {
|
||||||
|
s.streamer.engine = &eng
|
||||||
return gnet.None
|
return gnet.None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user