<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sse on hereticles</title><link>https://icle.es/tags/sse/</link><description>Recent content in Sse on hereticles</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 25 May 2026 14:39:35 +0100</lastBuildDate><atom:link href="https://icle.es/tags/sse/index.xml" rel="self" type="application/rss+xml"/><item><title>Only One Per Customer</title><link>https://icle.es/2026/05/25/only-one-per-customer/</link><pubDate>Mon, 25 May 2026 14:44:52 +0100</pubDate><guid>https://icle.es/2026/05/25/only-one-per-customer/</guid><description>&lt;p>&lt;a href="https://icle.es/endeavours/henge.md">henge&lt;/a> pushes config to edge devices. Semantically,
each device should connect once - except:&lt;/p>
&lt;ul>
&lt;li>What if a device is misconfigured with the details for another device&lt;/li>
&lt;li>What happens if a device disconnected, but we haven&amp;rsquo;t picked it up yet&lt;/li>
&lt;li>How about if a device has a zombie connection and decides to reconnect&lt;/li>
&lt;li>Any other cases I&amp;rsquo;d not thought about.&lt;/li>
&lt;/ul>
&lt;p>So, we need to figure out how to handle a second connection from the same
device.&lt;/p></description><content:encoded><![CDATA[<p><a href="https://icle.es/endeavours/henge.md">henge</a> pushes config to edge devices. Semantically,
each device should connect once - except:</p>
<ul>
<li>What if a device is misconfigured with the details for another device</li>
<li>What happens if a device disconnected, but we haven&rsquo;t picked it up yet</li>
<li>How about if a device has a zombie connection and decides to reconnect</li>
<li>Any other cases I&rsquo;d not thought about.</li>
</ul>
<p>So, we need to figure out how to handle a second connection from the same
device.</p>
<h2 id="allow-multiple-connections">Allow multiple connections</h2>
<p>For each device, we track multiple connections. Instead of a <code>map</code> to each
<code>channel</code>, we&rsquo;d have one to an array of <code>chan</code>s (<code>[]chan</code>)</p>
<p>Every operation on channel then becomes a loop.</p>
<p>For the case where each correct case is most likely to be one device to one
connection, this option has a lot of additional complexity, work and defect
surface area.</p>
<h2 id="reject-additional-connections">Reject additional connections</h2>
<p>This option is simpler. If a device is already connected, we reject any
additional connections.</p>
<p>This option is certainly simpler than allowing multiple connections. However, we
are dealing with a network and there are many reasons why a connection does not
close properly.</p>
<p>To be able to mitigate some of the issues, we&rsquo;d have to allow the user to
manually clear an open connection so that a device can re-connect.</p>
<p>The endpoint is easy enough to write, but requiring user intervention is not
something I like having as part of a product.</p>
<p>We could add a heartbeat, and close the connection down if it&rsquo;s doesn&rsquo;t receive
a heartbeat for a configured amount of time. This reduces the reliance on user
intervention by replacing it with wait time. Of course, it also now adds
complexity.</p>
<h2 id="close-existing-connection-on-reconnect">Close existing connection on reconnect</h2>
<p>How about on reconnect, we close any existing connections and reconnect to the
new request?</p>
<p>Instead of one <code>chan</code> per connection, we now have a <code>Subscription</code></p>
```go
type Subscription struct {
	stream   chan henge.ConfigChangedEvent // actual stream of events
	closeCmd chan struct{}                 // on msg, unsubscribe
}
```
<p>And the connection code involves a little more of a dance:</p>
```go
func (c *ConfigEventBroker) Subscribe(deviceId string) *Subscription {
	c.mu.Lock()
	defer c.mu.Unlock()
	sub, found := c.channels[deviceId]
	if !found {
		sub = &Subscription{
			stream:   make(chan henge.ConfigChangedEvent, 1),
			closeCmd: make(chan struct{}, 1),
		}
		c.channels[deviceId] = sub
	} else {
		// already connected
		// close previous connection
		sub.closeCmd <- struct{}{}
	}

	return sub
}
```
<p>and the connection itself respects the quit command:</p>
```go
	for {
		select {
		case snap := <-sub.stream:
			err = sendSSEEvent(w, f, Snapshot{
				Version: snap.Version,
				Values:  snap.Config,
			})
			if err != nil {
				slog.Warn("error while emitting sse event", "err", err)
				return
			}
		case <-sub.closeCmd:
			// client reconnected. We can close this one
			return
		case <-req.Context().Done():
			r.broker.Unsubscribe(deviceId)
			err = r.devices.SetConnected(deviceId, false)
			if err != nil {
				slog.Warn("unable to set connect status to disconnected", "err", err)
			}
			return
		}
```
<p>We don&rsquo;t have to:</p>
<ul>
<li>Track multiple connections</li>
<li>Have Heartbeats</li>
<li>Require user intervention</li>
</ul>
<p>In the event of rapid reconnect flurry coinciding with a slow handler, it should
still behave correctly, albeit slower. This rare edge case is accepted. If a
device is reconnecting that quickly to a slow handler, there are probably much
bigger problems at play.</p>
]]></content:encoded></item></channel></rss>