Enhance map update handling and connection stability
- Introduced mechanisms to detect stale connections in the map updates, allowing for automatic reconnection if no messages are received within a specified timeframe. - Updated the `refresh` method in `SmartTileLayer` to return a boolean indicating whether the tile was refreshed, improving the handling of tile updates. - Enhanced the `Send` method in the `Topic` struct to drop messages for full subscribers while keeping them subscribed, ensuring continuous delivery of future updates. - Added a keepalive mechanism in the `WatchGridUpdates` handler to maintain the connection and prevent timeouts.
This commit is contained in:
@@ -26,9 +26,10 @@ const (
|
||||
MultipartMaxMemory = 100 << 20 // 100 MB
|
||||
MergeMaxMemory = 500 << 20 // 500 MB
|
||||
ClientVersion = "4"
|
||||
SSETickInterval = 5 * time.Second
|
||||
SSETileChannelSize = 1000
|
||||
SSEMergeChannelSize = 5
|
||||
SSETickInterval = 1 * time.Second
|
||||
SSEKeepaliveInterval = 30 * time.Second
|
||||
SSETileChannelSize = 2000
|
||||
SSEMergeChannelSize = 5
|
||||
)
|
||||
|
||||
// App is the main application (map server) state.
|
||||
|
||||
@@ -93,16 +93,41 @@ func TestTopicClose(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicDropsSlowSubscriber(t *testing.T) {
|
||||
func TestTopicSkipsFullChannel(t *testing.T) {
|
||||
topic := &app.Topic[int]{}
|
||||
slow := make(chan *int) // unbuffered, will block
|
||||
slow := make(chan *int) // unbuffered, so Send will skip this subscriber
|
||||
fast := make(chan *int, 10)
|
||||
topic.Watch(slow)
|
||||
topic.Watch(fast)
|
||||
|
||||
val := 42
|
||||
topic.Send(&val) // should drop the slow subscriber
|
||||
topic.Send(&val) // slow is full (unbuffered), message dropped for slow only; fast receives
|
||||
topic.Send(&val)
|
||||
|
||||
// Fast subscriber got both messages
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case got := <-fast:
|
||||
if *got != 42 {
|
||||
t.Fatalf("fast got %d", *got)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("expected fast to have message %d", i+1)
|
||||
}
|
||||
}
|
||||
// Slow subscriber was skipped (channel full), not closed - channel still open and empty
|
||||
select {
|
||||
case _, ok := <-slow:
|
||||
if !ok {
|
||||
t.Fatal("slow channel should not be closed when subscriber is skipped")
|
||||
}
|
||||
t.Fatal("slow should have received no message")
|
||||
default:
|
||||
// slow is open and empty, which is correct
|
||||
}
|
||||
topic.Close()
|
||||
_, ok := <-slow
|
||||
if ok {
|
||||
t.Fatal("expected slow subscriber channel to be closed")
|
||||
t.Fatal("expected slow channel closed after topic.Close()")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,17 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
ticker := time.NewTicker(app.SSETickInterval)
|
||||
defer ticker.Stop()
|
||||
keepaliveTicker := time.NewTicker(app.SSEKeepaliveInterval)
|
||||
defer keepaliveTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-keepaliveTicker.C:
|
||||
if _, err := fmt.Fprint(rw, ": keepalive\n\n"); err != nil {
|
||||
return
|
||||
}
|
||||
flusher.Flush()
|
||||
case e, ok := <-c:
|
||||
if !ok {
|
||||
return
|
||||
@@ -99,7 +108,9 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) {
|
||||
case <-ticker.C:
|
||||
raw, _ := json.Marshal(tileCache)
|
||||
fmt.Fprint(rw, "data: ")
|
||||
_, _ = rw.Write(raw)
|
||||
if _, err := rw.Write(raw); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprint(rw, "\n\n")
|
||||
tileCache = tileCache[:0]
|
||||
flusher.Flush()
|
||||
|
||||
@@ -15,7 +15,9 @@ func (t *Topic[T]) Watch(c chan *T) {
|
||||
t.c = append(t.c, c)
|
||||
}
|
||||
|
||||
// Send broadcasts to all subscribers.
|
||||
// Send broadcasts to all subscribers. If a subscriber's channel is full,
|
||||
// the message is dropped for that subscriber only; the subscriber is not
|
||||
// removed, so the connection stays alive and later updates are still delivered.
|
||||
func (t *Topic[T]) Send(b *T) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
@@ -23,9 +25,7 @@ func (t *Topic[T]) Send(b *T) {
|
||||
select {
|
||||
case t.c[i] <- b:
|
||||
default:
|
||||
close(t.c[i])
|
||||
t.c[i] = t.c[len(t.c)-1]
|
||||
t.c = t.c[:len(t.c)-1]
|
||||
// Channel full: drop this message for this subscriber, keep them subscribed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user