Initial working fully go release

This commit is contained in:
Martino Ferrari
2026-04-30 23:01:01 +02:00
parent 6e51ffc5e1
commit 90669c5fd6
22 changed files with 2950 additions and 196 deletions
+61 -57
View File
@@ -53,19 +53,17 @@ type monState struct {
// - sid, dbfType, count, access: valid only while readyC is closed.
// - readyC: closed when CREATE_CHAN reply received; replaced on reconnect.
// - monitors: append-only (except removeMonitor); never cleared on reconnect.
// - pending: ioid → GET reply channel; drained (channels closed) on reconnect.
type chanState struct {
cid uint32
pvName string
mu sync.RWMutex
sid uint32 // server-assigned channel ID (0 until CREATE_CHAN reply)
dbfType int // native DBF field type
count uint32 // element count
access uint32 // AccessRead | AccessWrite bitmask
readyC chan struct{} // closed once CREATE_CHAN reply is received
monitors []*monState // active subscriptions
pending map[uint32]chan reply // ioid → one-shot GET callback
sid uint32 // server-assigned channel ID (0 until CREATE_CHAN reply)
dbfType int // native DBF field type
count uint32 // element count
access uint32 // AccessRead | AccessWrite bitmask
readyC chan struct{} // closed once CREATE_CHAN reply is received
monitors []*monState // active subscriptions
}
func newChanState(cid uint32, pvName string) *chanState {
@@ -73,7 +71,6 @@ func newChanState(cid uint32, pvName string) *chanState {
cid: cid,
pvName: pvName,
readyC: make(chan struct{}),
pending: make(map[uint32]chan reply),
}
}
@@ -81,16 +78,12 @@ func newChanState(cid uint32, pvName string) *chanState {
// It closes the current readyC (waking any waitReady callers so they can
// re-wait on the fresh channel) and then replaces it.
// Must be called before the circuit sends CREATE_CHAN on the new conn.
// In-flight GET requests are tracked in circuit.byIOID and cleared there.
func (cs *chanState) resetForReconnect() {
cs.mu.Lock()
cs.sid = 0
old := cs.readyC
cs.readyC = make(chan struct{})
// Drain pending GETs — they will receive zero reply (ok=false).
for id, ch := range cs.pending {
close(ch)
delete(cs.pending, id)
}
cs.mu.Unlock()
// Close the old readyC outside the lock to avoid deadlock with waitReady.
select {
@@ -150,7 +143,9 @@ type circuit struct {
mu sync.RWMutex
channels []*chanState // append-only; survive reconnect
byCID map[uint32]*chanState // cid → chanState (fast lookup)
bySID map[uint32]*chanState // sid → chanState (fast lookup)
bySubID map[uint32]*monState // subID → monState (fast event dispatch)
byIOID map[uint32]chan reply // ioid → GET/PUT callback (circuit-wide)
writeQ chan []byte // serialised outbound message queue
seq atomic.Uint32 // shared ID sequence (cid, ioid, subID)
@@ -165,7 +160,9 @@ func newCircuit(ctx context.Context, addr, clientName, hostName string) *circuit
ctx: cctx,
cancel: cancel,
byCID: make(map[uint32]*chanState),
bySID: make(map[uint32]*chanState),
bySubID: make(map[uint32]*monState),
byIOID: make(map[uint32]chan reply),
writeQ: make(chan []byte, writeQueueDepth),
}
go c.run()
@@ -204,6 +201,12 @@ func (c *circuit) run() {
// Snapshot channels and reset their per-connection state.
c.mu.Lock()
c.bySID = make(map[uint32]*chanState)
// Close all in-flight GET reply channels so waiters unblock with ok=false.
for _, ch := range c.byIOID {
close(ch)
}
c.byIOID = make(map[uint32]chan reply)
for _, cs := range c.channels {
cs.resetForReconnect()
}
@@ -361,9 +364,13 @@ func (c *circuit) dispatch(conn net.Conn, hdr proto.Header, payload []byte) {
case proto.CmdCreateChan:
// Parameter1 = cid (echoed), Parameter2 = SID assigned by server.
c.mu.RLock()
c.mu.Lock()
cs, ok := c.byCID[hdr.Parameter1]
c.mu.RUnlock()
if ok {
c.bySID[hdr.Parameter2] = cs
}
c.mu.Unlock()
if !ok {
return
}
@@ -425,21 +432,23 @@ func (c *circuit) dispatch(conn net.Conn, hdr proto.Header, payload []byte) {
}
case proto.CmdEventAdd:
// Monitor update: Parameter1 = subscriptionID.
// Monitor update: server puts subscription ID in Parameter2 (m_available).
// Parameter1 carries ECA_NORMAL (1) as a status code.
subID := hdr.Parameter2
c.mu.RLock()
ms, ok := c.bySubID[hdr.Parameter1]
ms, ok := c.bySubID[subID]
c.mu.RUnlock()
if !ok {
dbg("CA EVENT_ADD for unknown subID", "subID", hdr.Parameter1)
dbg("CA EVENT_ADD for unknown subID", "subID", subID)
return
}
tv, ok := proto.DecodeTimeValue(hdr.DataType, hdr.DataCount, payload)
if !ok {
dbg("CA EVENT_ADD decode failed", "subID", hdr.Parameter1,
dbg("CA EVENT_ADD decode failed", "subID", subID,
"dbrType", hdr.DataType, "count", hdr.DataCount, "payloadLen", len(payload))
return
}
dbg("CA EVENT_ADD value", "subID", hdr.Parameter1, "dbrType", hdr.DataType,
dbg("CA EVENT_ADD value", "subID", subID, "dbrType", hdr.DataType,
"double", tv.Double, "severity", tv.Severity)
select {
case ms.ch <- tv:
@@ -447,25 +456,20 @@ func (c *circuit) dispatch(conn net.Conn, hdr proto.Header, payload []byte) {
}
case proto.CmdReadNotify:
// GET reply: Parameter1 = ioid.
ioid := hdr.Parameter1
c.mu.RLock()
var replyCh chan reply
var found bool
for _, cs := range c.channels {
cs.mu.Lock()
if ch, exists := cs.pending[ioid]; exists {
delete(cs.pending, ioid)
replyCh = ch
found = true
}
cs.mu.Unlock()
if found {
break
}
// Parameter2 = ioid (our request ID echoed back by server).
ioid := hdr.Parameter2
c.mu.Lock()
replyCh, ok := c.byIOID[ioid]
if ok {
delete(c.byIOID, ioid)
}
c.mu.RUnlock()
if found {
c.mu.Unlock()
dbg("CA CmdReadNotify", "ioid", ioid, "found", ok, "dbrType", hdr.DataType,
"count", hdr.DataCount, "payloadSize", hdr.PayloadSize)
if ok {
select {
case replyCh <- reply{hdr: hdr, payload: payload, ok: true}:
default:
@@ -615,9 +619,9 @@ func (c *circuit) getRaw(ctx context.Context, cs *chanState, dbrType uint16, cou
ioid := c.nextID()
replyCh := make(chan reply, 1)
cs.mu.Lock()
cs.pending[ioid] = replyCh
cs.mu.Unlock()
c.mu.Lock()
c.byIOID[ioid] = replyCh
c.mu.Unlock()
msg := proto.BuildMessage(proto.Header{
Command: proto.CmdReadNotify,
@@ -630,9 +634,9 @@ func (c *circuit) getRaw(ctx context.Context, cs *chanState, dbrType uint16, cou
select {
case c.writeQ <- msg:
case <-ctx.Done():
cs.mu.Lock()
delete(cs.pending, ioid)
cs.mu.Unlock()
c.mu.Lock()
delete(c.byIOID, ioid)
c.mu.Unlock()
return proto.Header{}, nil, ctx.Err()
}
@@ -643,9 +647,9 @@ func (c *circuit) getRaw(ctx context.Context, cs *chanState, dbrType uint16, cou
}
return r.hdr, r.payload, nil
case <-ctx.Done():
cs.mu.Lock()
delete(cs.pending, ioid)
cs.mu.Unlock()
c.mu.Lock()
delete(c.byIOID, ioid)
c.mu.Unlock()
return proto.Header{}, nil, ctx.Err()
}
}
@@ -667,9 +671,9 @@ func (c *circuit) get(ctx context.Context, cs *chanState, dbrType uint16, count
ioid := c.nextID()
replyCh := make(chan reply, 1)
cs.mu.Lock()
cs.pending[ioid] = replyCh
cs.mu.Unlock()
c.mu.Lock()
c.byIOID[ioid] = replyCh
c.mu.Unlock()
msg := proto.BuildMessage(proto.Header{
Command: proto.CmdReadNotify,
@@ -682,9 +686,9 @@ func (c *circuit) get(ctx context.Context, cs *chanState, dbrType uint16, count
select {
case c.writeQ <- msg:
case <-ctx.Done():
cs.mu.Lock()
delete(cs.pending, ioid)
cs.mu.Unlock()
c.mu.Lock()
delete(c.byIOID, ioid)
c.mu.Unlock()
return proto.TimeValue{}, ctx.Err()
}
@@ -699,9 +703,9 @@ func (c *circuit) get(ctx context.Context, cs *chanState, dbrType uint16, count
}
return tv, nil
case <-ctx.Done():
cs.mu.Lock()
delete(cs.pending, ioid)
cs.mu.Unlock()
c.mu.Lock()
delete(c.byIOID, ioid)
c.mu.Unlock()
return proto.TimeValue{}, ctx.Err()
}
}