Initial working fully go release
This commit is contained in:
+61
-57
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-18
@@ -85,7 +85,7 @@ func caString(b []byte) string {
|
||||
return string(b[:idx])
|
||||
}
|
||||
|
||||
// DBR_TIME_* wire layouts (all big-endian):
|
||||
// DBR_TIME_* wire layouts (all big-endian, matching EPICS db_access.h structs):
|
||||
//
|
||||
// Common header (12 bytes):
|
||||
// [0:2] int16 status
|
||||
@@ -93,13 +93,13 @@ func caString(b []byte) string {
|
||||
// [4:8] uint32 secPastEpoch
|
||||
// [8:12] uint32 nsec
|
||||
//
|
||||
// DBR_TIME_DOUBLE (type 25): 4-byte RISC pad at [12:16], float64 at [16:24]. Total=24.
|
||||
// DBR_TIME_FLOAT (type 20): float32 at [12:16]. Total=16.
|
||||
// DBR_TIME_LONG (type 22): int32 at [12:16]. Total=16.
|
||||
// DBR_TIME_SHORT (type 19): int16 at [12:14], 2-byte pad [14:16]. Total=16.
|
||||
// DBR_TIME_ENUM (type 23): uint16 at [12:14], 2-byte pad [14:16]. Total=16.
|
||||
// DBR_TIME_CHAR (type 24): uint8 at [12:13], 3-byte pad [13:16]. Total=16.
|
||||
// DBR_TIME_STRING (type 21): [40]byte at [12:52]. Total=52.
|
||||
// DBR_TIME_STRING (type 14): [40]byte at [12:52]. Total=52.
|
||||
// DBR_TIME_SHORT (type 15): 2-byte RISC pad at [12:14], int16 at [14:16]. Total=16.
|
||||
// DBR_TIME_FLOAT (type 16): float32 at [12:16]. Total=16.
|
||||
// DBR_TIME_ENUM (type 17): 2-byte RISC pad at [12:14], uint16 at [14:16]. Total=16.
|
||||
// DBR_TIME_CHAR (type 18): RISC_pad0[12:14], RISC_pad1[14], uint8 at [15]. Total=16.
|
||||
// DBR_TIME_LONG (type 19): int32 at [12:16]. Total=16.
|
||||
// DBR_TIME_DOUBLE (type 20): 4-byte RISC pad at [12:16], float64 at [16:24]. Total=24.
|
||||
|
||||
// DecodeTimeValue decodes a DBR_TIME_* payload.
|
||||
// dbrType is one of the DBRTime* constants; payload is the full message payload
|
||||
@@ -158,24 +158,27 @@ func DecodeTimeValue(dbrType uint16, count uint32, payload []byte) (TimeValue, b
|
||||
tv.Double = float64(tv.Long)
|
||||
|
||||
case DBRTimeShort:
|
||||
// 2-byte RISC pad at [12:14], value at [14:16]
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Short = int16(binary.BigEndian.Uint16(payload[12:]))
|
||||
tv.Short = int16(binary.BigEndian.Uint16(payload[14:]))
|
||||
tv.Double = float64(tv.Short)
|
||||
|
||||
case DBRTimeEnum:
|
||||
// 2-byte RISC pad at [12:14], value at [14:16]
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Enum = binary.BigEndian.Uint16(payload[12:])
|
||||
tv.Enum = binary.BigEndian.Uint16(payload[14:])
|
||||
tv.Double = float64(tv.Enum)
|
||||
|
||||
case DBRTimeChar:
|
||||
// RISC_pad0[12:14], RISC_pad1[14], value[15] per db_access.h dbr_time_char
|
||||
if len(payload) < 16 {
|
||||
return TimeValue{}, false
|
||||
}
|
||||
tv.Char = payload[12]
|
||||
tv.Char = payload[15]
|
||||
tv.Double = float64(tv.Char)
|
||||
|
||||
case DBRTimeString:
|
||||
@@ -427,15 +430,15 @@ func EncodeString(v string) []byte {
|
||||
// EncodeEventMask builds the 16-byte payload for a CA_PROTO_EVENT_ADD request.
|
||||
// mask is typically DBEDefault (DBEValue | DBEAlarm).
|
||||
//
|
||||
// Wire layout (16 bytes, all big-endian):
|
||||
// Wire layout matches caProto.h struct mon_info (all big-endian):
|
||||
//
|
||||
// [0:4] float32 m_lval (not used, zero)
|
||||
// [4:8] float32 p_delta (delta trigger, zero = disabled)
|
||||
// [8:12] float32 p_final (final trigger, zero = disabled)
|
||||
// [12:14] int16 p_count (element count, zero = use PV's count)
|
||||
// [14:16] int16 m_mask (DBE_VALUE | DBE_ALARM | ...)
|
||||
// [0:4] float32 m_lval (low delta, zero = disabled)
|
||||
// [4:8] float32 m_hval (high delta, zero = disabled)
|
||||
// [8:12] float32 m_toval (period between samples, zero = disabled)
|
||||
// [12:14] uint16 m_mask (DBE_VALUE | DBE_ALARM | ...)
|
||||
// [14:16] uint16 m_pad (alignment padding)
|
||||
func EncodeEventMask(mask uint16) []byte {
|
||||
b := make([]byte, 16)
|
||||
binary.BigEndian.PutUint16(b[14:], mask) // m_mask is at offset 14, not 12
|
||||
binary.BigEndian.PutUint16(b[12:], mask) // m_mask is at offset 12 per caProto.h
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -74,14 +74,15 @@ const (
|
||||
)
|
||||
|
||||
// DBR_TIME_* types (value + timestamp + alarm status; used in EVENT_ADD).
|
||||
// Numbers from db_access.h: STRING=14, SHORT/INT=15, FLOAT=16, ENUM=17, CHAR=18, LONG=19, DOUBLE=20.
|
||||
const (
|
||||
DBRTimeString = 21
|
||||
DBRTimeShort = 19
|
||||
DBRTimeFloat = 20
|
||||
DBRTimeEnum = 23
|
||||
DBRTimeChar = 24
|
||||
DBRTimeLong = 22
|
||||
DBRTimeDouble = 25
|
||||
DBRTimeString = 14
|
||||
DBRTimeShort = 15
|
||||
DBRTimeFloat = 16
|
||||
DBRTimeEnum = 17
|
||||
DBRTimeChar = 18
|
||||
DBRTimeLong = 19
|
||||
DBRTimeDouble = 20
|
||||
)
|
||||
|
||||
// DBR_CTRL_* types (full control info: units, limits, enum strings; used in READ_NOTIFY).
|
||||
|
||||
@@ -159,7 +159,7 @@ func TestDecodeTimeString(t *testing.T) {
|
||||
|
||||
func TestDecodeTimeEnum(t *testing.T) {
|
||||
hdr := buildTimeHeader(0, 0, 0, 0)
|
||||
val := []byte{0x00, 0x03, 0x00, 0x00} // enum=3 + 2-byte pad
|
||||
val := []byte{0x00, 0x00, 0x00, 0x03} // 2-byte RISC pad + enum=3
|
||||
payload := append(hdr, val...)
|
||||
|
||||
tv, ok := proto.DecodeTimeValue(proto.DBRTimeEnum, 1, payload)
|
||||
@@ -307,14 +307,14 @@ func TestEncodeEventMask(t *testing.T) {
|
||||
if len(b) != 16 {
|
||||
t.Fatalf("len = %d, want 16", len(b))
|
||||
}
|
||||
// m_mask is at offset 14 (after m_lval[0:4], p_delta[4:8], p_final[8:12], p_count[12:14]).
|
||||
mask := binary.BigEndian.Uint16(b[14:])
|
||||
// m_mask is at offset 12 per caProto.h struct mon_info.
|
||||
mask := binary.BigEndian.Uint16(b[12:])
|
||||
if mask != proto.DBEDefault {
|
||||
t.Errorf("mask = 0x%02X, want 0x%02X", mask, proto.DBEDefault)
|
||||
}
|
||||
// p_count at [12:14] must be zero.
|
||||
if pc := binary.BigEndian.Uint16(b[12:]); pc != 0 {
|
||||
t.Errorf("p_count = %d, want 0", pc)
|
||||
// m_pad at [14:16] must be zero.
|
||||
if pad := binary.BigEndian.Uint16(b[14:]); pad != 0 {
|
||||
t.Errorf("m_pad = %d, want 0", pad)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-7
@@ -338,9 +338,11 @@ func (s *Server) handleConn(conn net.Conn) {
|
||||
ioid := hdr.Parameter2
|
||||
|
||||
var pvName string
|
||||
for _, info := range channels {
|
||||
var cid uint32
|
||||
for c, info := range channels {
|
||||
if info.sid == sid {
|
||||
pvName = info.pvName
|
||||
cid = c
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -368,7 +370,8 @@ func (s *Server) handleConn(conn net.Conn) {
|
||||
Command: proto.CmdReadNotify,
|
||||
DataType: hdr.DataType,
|
||||
DataCount: hdr.DataCount,
|
||||
Parameter1: ioid,
|
||||
Parameter1: cid, // echo client channel ID (matches real EPICS IOC)
|
||||
Parameter2: ioid, // echo ioid so client can match the reply
|
||||
}, replyPayload)
|
||||
conn.Write(reply)
|
||||
|
||||
@@ -430,7 +433,8 @@ func (s *Server) buildEventMsg(sub *serverSub, pv *serverPV) []byte {
|
||||
Command: proto.CmdEventAdd,
|
||||
DataType: sub.dbrType,
|
||||
DataCount: sub.count,
|
||||
Parameter1: sub.subID,
|
||||
Parameter1: 1, // ECA_NORMAL
|
||||
Parameter2: sub.subID,
|
||||
}, payload)
|
||||
}
|
||||
|
||||
@@ -461,12 +465,12 @@ func (s *Server) encodeTimeValue(dbrType uint16, _ uint32, val any) []byte {
|
||||
binary.BigEndian.PutUint32(body, uint32(int32(toF64(val))))
|
||||
|
||||
case proto.DBRTimeShort, proto.DBRTimeEnum:
|
||||
body = make([]byte, 4) // 2 value + 2 pad
|
||||
binary.BigEndian.PutUint16(body, uint16(int16(toF64(val))))
|
||||
body = make([]byte, 4) // 2-byte RISC pad + 2-byte value
|
||||
binary.BigEndian.PutUint16(body[2:], uint16(int16(toF64(val))))
|
||||
|
||||
case proto.DBRTimeChar:
|
||||
body = make([]byte, 4) // 1 value + 3 pad
|
||||
body[0] = byte(uint8(toF64(val)))
|
||||
body = make([]byte, 4) // RISC_pad0[0:2] + RISC_pad1[2] + value[3]
|
||||
body[3] = byte(uint8(toF64(val)))
|
||||
|
||||
case proto.DBRTimeString:
|
||||
body = make([]byte, 40)
|
||||
|
||||
Reference in New Issue
Block a user