+1
-4
example/main.go
+1
-4
example/main.go
+2
-1
example/server/main.go
+2
-1
example/server/main.go
+16
pubsub/message.go
+16
pubsub/message.go
···
4
4
type Message struct {
5
5
Topic string `json:"topic"`
6
6
Data []byte `json:"data"`
7
+
8
+
ack chan bool
9
+
}
10
+
11
+
// NewMessage creates a new message
12
+
func NewMessage(topic string, data []byte) *Message {
13
+
return &Message{
14
+
Topic: topic,
15
+
Data: data,
16
+
ack: make(chan bool),
17
+
}
18
+
}
19
+
20
+
// Ack will send the provided value of the ack to the server
21
+
func (m *Message) Ack(ack bool) {
22
+
m.ack <- ack
7
23
}
+1
-1
pubsub/publisher.go
+1
-1
pubsub/publisher.go
···
39
39
}
40
40
41
41
// Publish will publish the given message to the server
42
-
func (p *Publisher) PublishMessage(message Message) error {
42
+
func (p *Publisher) PublishMessage(message *Message) error {
43
43
op := func(conn net.Conn) error {
44
44
// send topic first
45
45
topic := fmt.Sprintf("topic:%s", message.Topic)
+23
-17
pubsub/subscriber.go
+23
-17
pubsub/subscriber.go
···
143
143
// Consumer allows the consumption of messages. If during the consumer receiving messages from the
144
144
// server an error occurs, it will be stored in Err
145
145
type Consumer struct {
146
-
msgs chan Message
146
+
msgs chan *Message
147
147
// TODO: better error handling? Maybe a channel of errors?
148
148
Err error
149
149
}
150
150
151
151
// Messages returns a channel in which this consumer will put messages onto. It is safe to range over the channel since it will be closed once
152
152
// the consumer has finished either due to an error or from being cancelled.
153
-
func (c *Consumer) Messages() <-chan Message {
153
+
func (c *Consumer) Messages() <-chan *Message {
154
154
return c.msgs
155
155
}
156
156
···
158
158
// to read the messages
159
159
func (s *Subscriber) Consume(ctx context.Context) *Consumer {
160
160
consumer := &Consumer{
161
-
msgs: make(chan Message),
161
+
msgs: make(chan *Message),
162
162
}
163
163
164
164
go s.consume(ctx, consumer)
···
173
173
return
174
174
}
175
175
176
-
msg, err := s.readMessage()
176
+
err := s.readMessage(consumer.msgs)
177
177
if err != nil {
178
178
consumer.Err = err
179
179
return
180
180
}
181
-
182
-
if msg != nil {
183
-
consumer.msgs <- *msg
184
-
}
185
181
}
186
182
}
187
183
188
-
func (s *Subscriber) readMessage() (*Message, error) {
189
-
var msg *Message
184
+
func (s *Subscriber) readMessage(msgChan chan *Message) error {
185
+
// var msg *Message
190
186
op := func(conn net.Conn) error {
191
187
err := s.conn.SetReadDeadline(time.Now().Add(time.Second))
192
188
if err != nil {
···
225
221
return err
226
222
}
227
223
228
-
msg = &Message{
229
-
Data: dataBuf,
230
-
Topic: string(topicBuf),
224
+
msg := NewMessage(string(topicBuf), dataBuf)
225
+
226
+
msgChan <- msg
227
+
228
+
ack := <-msg.ack
229
+
230
+
ackMessage := server.Nack
231
+
if ack {
232
+
ackMessage = server.Ack
231
233
}
232
234
233
-
return nil
235
+
err = binary.Write(s.conn, binary.BigEndian, ackMessage)
236
+
if err != nil {
237
+
return fmt.Errorf("failed to ack/nack message: %w", err)
238
+
}
234
239
240
+
return nil
235
241
}
236
242
237
243
err := s.connOperation(op)
238
244
if err != nil {
239
245
var neterr net.Error
240
246
if errors.As(err, &neterr) && neterr.Timeout() {
241
-
return nil, nil
247
+
return nil
242
248
}
243
-
return nil, err
249
+
return err
244
250
}
245
251
246
-
return msg, err
252
+
return err
247
253
}
248
254
249
255
func (s *Subscriber) connOperation(op connOpp) error {
+88
-36
pubsub/subscriber_test.go
+88
-36
pubsub/subscriber_test.go
···
19
19
)
20
20
21
21
func createServer(t *testing.T) {
22
-
server, err := server.New(serverAddr)
22
+
server, err := server.New(serverAddr, time.Millisecond*100, time.Millisecond*100)
23
23
require.NoError(t, err)
24
24
25
25
t.Cleanup(func() {
···
105
105
consumer := sub.Consume(ctx)
106
106
require.NoError(t, err)
107
107
108
-
var receivedMessages []Message
108
+
var receivedMessages []*Message
109
109
consumerFinCh := make(chan struct{})
110
110
go func() {
111
111
for msg := range consumer.Messages() {
112
+
msg.Ack(true)
112
113
receivedMessages = append(receivedMessages, msg)
113
114
}
114
115
115
-
require.NoError(t, err)
116
116
consumerFinCh <- struct{}{}
117
117
}()
118
118
···
125
125
publisher.Close()
126
126
})
127
127
128
-
msg := Message{
129
-
Topic: topicA,
130
-
Data: []byte("hello world"),
131
-
}
128
+
msg := NewMessage(topicA, []byte("hello world"))
132
129
133
130
err = publisher.PublishMessage(msg)
134
131
require.NoError(t, err)
···
151
148
}
152
149
153
150
func TestPublishAndSubscribe(t *testing.T) {
154
-
createServer(t)
155
-
156
-
sub, err := NewSubscriber(serverAddr)
157
-
require.NoError(t, err)
158
-
159
-
t.Cleanup(func() {
160
-
sub.Close()
161
-
})
162
-
163
-
topics := []string{topicA, topicB}
164
-
165
-
err = sub.SubscribeToTopics(topics)
166
-
require.NoError(t, err)
167
-
168
-
ctx, cancel := context.WithCancel(context.Background())
169
-
t.Cleanup(func() {
170
-
cancel()
171
-
})
172
-
173
-
consumer := sub.Consume(ctx)
174
-
require.NoError(t, err)
151
+
consumer, cancel := setupConsumer(t)
175
152
176
-
var receivedMessages []Message
153
+
var receivedMessages []*Message
177
154
178
155
consumerFinCh := make(chan struct{})
179
156
go func() {
180
157
for msg := range consumer.Messages() {
158
+
msg.Ack(true)
181
159
receivedMessages = append(receivedMessages, msg)
182
160
}
183
161
184
-
require.NoError(t, err)
185
162
consumerFinCh <- struct{}{}
186
163
}()
187
164
···
192
169
})
193
170
194
171
// send some messages
195
-
sentMessages := make([]Message, 0, 10)
172
+
sentMessages := make([]*Message, 0, 10)
196
173
for i := 0; i < 10; i++ {
197
-
msg := Message{
198
-
Topic: topicA,
199
-
Data: []byte(fmt.Sprintf("message %d", i)),
200
-
}
174
+
msg := NewMessage(topicA, []byte(fmt.Sprintf("message %d", i)))
201
175
202
176
sentMessages = append(sentMessages, msg)
203
177
···
212
186
select {
213
187
case <-consumerFinCh:
214
188
break
215
-
case <-time.After(time.Second):
189
+
case <-time.After(time.Second * 5):
216
190
t.Fatal("timed out waiting for consumer to read messages")
217
191
}
218
192
193
+
// THIS IS SO HACKY
194
+
for _, msg := range receivedMessages {
195
+
msg.ack = nil
196
+
}
197
+
198
+
for _, msg := range sentMessages {
199
+
msg.ack = nil
200
+
}
201
+
219
202
assert.ElementsMatch(t, receivedMessages, sentMessages)
220
203
}
204
+
205
+
func TestPublishAndSubscribeNackMessage(t *testing.T) {
206
+
consumer, cancel := setupConsumer(t)
207
+
208
+
var receivedMessages []*Message
209
+
210
+
consumerFinCh := make(chan struct{})
211
+
timesMsgWasReceived := 0
212
+
go func() {
213
+
for msg := range consumer.Messages() {
214
+
msg.Ack(false)
215
+
timesMsgWasReceived++
216
+
}
217
+
218
+
consumerFinCh <- struct{}{}
219
+
}()
220
+
221
+
publisher, err := NewPublisher("localhost:9999")
222
+
require.NoError(t, err)
223
+
t.Cleanup(func() {
224
+
publisher.Close()
225
+
})
226
+
227
+
// send a message
228
+
msg := NewMessage(topicA, []byte("hello world"))
229
+
230
+
err = publisher.PublishMessage(msg)
231
+
require.NoError(t, err)
232
+
233
+
// give the consumer some time to read the messages -- TODO: make better!
234
+
time.Sleep(time.Millisecond * 500)
235
+
cancel()
236
+
237
+
select {
238
+
case <-consumerFinCh:
239
+
break
240
+
case <-time.After(time.Second * 5):
241
+
t.Fatal("timed out waiting for consumer to read messages")
242
+
}
243
+
244
+
assert.Empty(t, receivedMessages)
245
+
assert.Equal(t, 5, timesMsgWasReceived)
246
+
}
247
+
248
+
func setupConsumer(t *testing.T) (*Consumer, context.CancelFunc) {
249
+
createServer(t)
250
+
251
+
sub, err := NewSubscriber(serverAddr)
252
+
require.NoError(t, err)
253
+
254
+
t.Cleanup(func() {
255
+
sub.Close()
256
+
})
257
+
258
+
topics := []string{topicA, topicB}
259
+
260
+
err = sub.SubscribeToTopics(topics)
261
+
require.NoError(t, err)
262
+
263
+
ctx, cancel := context.WithCancel(context.Background())
264
+
t.Cleanup(func() {
265
+
cancel()
266
+
})
267
+
268
+
consumer := sub.Consume(ctx)
269
+
require.NoError(t, err)
270
+
271
+
return consumer, cancel
272
+
}
+17
-9
server/server.go
+17
-9
server/server.go
···
23
23
Subscribe Action = 1
24
24
Unsubscribe Action = 2
25
25
Publish Action = 3
26
+
Ack Action = 4
27
+
Nack Action = 5
26
28
)
27
29
28
30
// Status represents the status of a request
···
54
56
55
57
mu sync.Mutex
56
58
topics map[string]*topic
59
+
60
+
ackDelay time.Duration
61
+
ackTimeout time.Duration
57
62
}
58
63
59
64
// New creates and starts a new server
60
-
func New(Addr string) (*Server, error) {
65
+
func New(Addr string, ackDelay, ackTimeout time.Duration) (*Server, error) {
61
66
lis, err := net.Listen("tcp", Addr)
62
67
if err != nil {
63
68
return nil, fmt.Errorf("failed to listen: %w", err)
64
69
}
65
70
66
71
srv := &Server{
67
-
lis: lis,
68
-
topics: map[string]*topic{},
72
+
lis: lis,
73
+
topics: map[string]*topic{},
74
+
ackDelay: ackDelay,
75
+
ackTimeout: ackTimeout,
69
76
}
70
77
71
78
go srv.start()
···
337
344
t = newTopic(topicName)
338
345
}
339
346
340
-
t.subscriptions[peer.Addr()] = subscriber{
341
-
peer: peer,
342
-
currentOffset: 0,
343
-
}
347
+
t.subscriptions[peer.Addr()] = newSubscriber(peer, topicName, s.ackDelay, s.ackTimeout)
344
348
345
349
s.topics[topicName] = t
346
350
}
···
388
392
var action Action
389
393
op := func(conn net.Conn) error {
390
394
if timeout > 0 {
391
-
err := conn.SetReadDeadline(time.Now().Add(timeout))
392
-
if err != nil {
395
+
if err := conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
393
396
slog.Error("failed to set connection read deadline", "error", err, "peer", peer.Addr())
394
397
}
398
+
defer func() {
399
+
if err := conn.SetReadDeadline(time.Time{}); err != nil {
400
+
slog.Error("failed to reset connection read deadline", "error", err, "peer", peer.Addr())
401
+
}
402
+
}()
395
403
}
396
404
397
405
err := binary.Read(conn, binary.BigEndian, &action)
+217
-2
server/server_test.go
+217
-2
server/server_test.go
···
18
18
topicC = "topic c"
19
19
20
20
serverAddr = ":6666"
21
+
22
+
ackDelay = time.Millisecond * 100
23
+
ackTimeout = time.Millisecond * 100
21
24
)
22
25
23
26
func createServer(t *testing.T) *Server {
24
-
srv, err := New(serverAddr)
27
+
srv, err := New(serverAddr, ackDelay, ackTimeout)
25
28
require.NoError(t, err)
26
29
27
30
t.Cleanup(func() {
···
35
38
srv := createServer(t)
36
39
srv.topics[topicName] = &topic{
37
40
name: topicName,
38
-
subscriptions: make(map[net.Addr]subscriber),
41
+
subscriptions: make(map[net.Addr]*subscriber),
39
42
}
40
43
41
44
return srv
···
268
271
buf := make([]byte, dataLen)
269
272
n, err := conn.Read(buf)
270
273
require.NoError(t, err)
274
+
271
275
require.Equal(t, int(dataLen), n)
272
276
273
277
assert.Equal(t, messageData, string(buf))
278
+
279
+
err = binary.Write(conn, binary.BigEndian, Ack)
280
+
require.NoError(t, err)
274
281
}
275
282
}
276
283
···
314
321
require.Equal(t, int(dataLen), n)
315
322
316
323
results = append(results, string(buf))
324
+
325
+
err = binary.Write(subscriberConn, binary.BigEndian, Ack)
326
+
require.NoError(t, err)
317
327
}
318
328
319
329
assert.ElementsMatch(t, results, messages)
···
346
356
t.Fatal(fmt.Errorf("timed out waiting for subscriber to read messages"))
347
357
}
348
358
}
359
+
360
+
func TestSendsDataToTopicSubscriberNacksThenAcks(t *testing.T) {
361
+
_ = createServer(t)
362
+
363
+
subscriberConn := createConnectionAndSubscribe(t, []string{topicA, topicB})
364
+
365
+
publisherConn, err := net.Dial("tcp", fmt.Sprintf("localhost%s", serverAddr))
366
+
require.NoError(t, err)
367
+
368
+
err = binary.Write(publisherConn, binary.BigEndian, Publish)
369
+
require.NoError(t, err)
370
+
371
+
topic := fmt.Sprintf("topic:%s", topicA)
372
+
messageData := "hello world"
373
+
374
+
// send topic first
375
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(topic)))
376
+
require.NoError(t, err)
377
+
_, err = publisherConn.Write([]byte(topic))
378
+
require.NoError(t, err)
379
+
380
+
// now send the data
381
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(messageData)))
382
+
require.NoError(t, err)
383
+
n, err := publisherConn.Write([]byte(messageData))
384
+
require.NoError(t, err)
385
+
require.Equal(t, len(messageData), n)
386
+
387
+
// check the subsribers got the data
388
+
readMessage := func(conn net.Conn, ack Action) {
389
+
var topicLen uint64
390
+
err = binary.Read(conn, binary.BigEndian, &topicLen)
391
+
require.NoError(t, err)
392
+
393
+
topicBuf := make([]byte, topicLen)
394
+
_, err = conn.Read(topicBuf)
395
+
require.NoError(t, err)
396
+
assert.Equal(t, topicA, string(topicBuf))
397
+
398
+
var dataLen uint64
399
+
err = binary.Read(conn, binary.BigEndian, &dataLen)
400
+
require.NoError(t, err)
401
+
402
+
buf := make([]byte, dataLen)
403
+
n, err := conn.Read(buf)
404
+
require.NoError(t, err)
405
+
406
+
require.Equal(t, int(dataLen), n)
407
+
408
+
assert.Equal(t, messageData, string(buf))
409
+
410
+
err = binary.Write(conn, binary.BigEndian, ack)
411
+
require.NoError(t, err)
412
+
}
413
+
414
+
// NACK the message and then ack it
415
+
readMessage(subscriberConn, Nack)
416
+
readMessage(subscriberConn, Ack)
417
+
// reading for another message should now timeout but give enough time for the ack delay to kick in
418
+
// should the second read of the message not have been ack'd properly
419
+
var topicLen uint64
420
+
_ = subscriberConn.SetReadDeadline(time.Now().Add(ackDelay + time.Millisecond*100))
421
+
err = binary.Read(subscriberConn, binary.BigEndian, &topicLen)
422
+
require.Error(t, err)
423
+
}
424
+
425
+
func TestSendsDataToTopicSubscriberDoesntAckMessage(t *testing.T) {
426
+
_ = createServer(t)
427
+
428
+
subscriberConn := createConnectionAndSubscribe(t, []string{topicA, topicB})
429
+
430
+
publisherConn, err := net.Dial("tcp", fmt.Sprintf("localhost%s", serverAddr))
431
+
require.NoError(t, err)
432
+
433
+
err = binary.Write(publisherConn, binary.BigEndian, Publish)
434
+
require.NoError(t, err)
435
+
436
+
topic := fmt.Sprintf("topic:%s", topicA)
437
+
messageData := "hello world"
438
+
439
+
// send topic first
440
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(topic)))
441
+
require.NoError(t, err)
442
+
_, err = publisherConn.Write([]byte(topic))
443
+
require.NoError(t, err)
444
+
445
+
// now send the data
446
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(messageData)))
447
+
require.NoError(t, err)
448
+
n, err := publisherConn.Write([]byte(messageData))
449
+
require.NoError(t, err)
450
+
require.Equal(t, len(messageData), n)
451
+
452
+
// check the subsribers got the data
453
+
readMessage := func(conn net.Conn, ack bool) {
454
+
var topicLen uint64
455
+
err = binary.Read(conn, binary.BigEndian, &topicLen)
456
+
require.NoError(t, err)
457
+
458
+
topicBuf := make([]byte, topicLen)
459
+
_, err = conn.Read(topicBuf)
460
+
require.NoError(t, err)
461
+
assert.Equal(t, topicA, string(topicBuf))
462
+
463
+
var dataLen uint64
464
+
err = binary.Read(conn, binary.BigEndian, &dataLen)
465
+
require.NoError(t, err)
466
+
467
+
buf := make([]byte, dataLen)
468
+
n, err := conn.Read(buf)
469
+
require.NoError(t, err)
470
+
471
+
require.Equal(t, int(dataLen), n)
472
+
473
+
assert.Equal(t, messageData, string(buf))
474
+
475
+
if ack {
476
+
err = binary.Write(conn, binary.BigEndian, Ack)
477
+
require.NoError(t, err)
478
+
return
479
+
}
480
+
}
481
+
482
+
// don't send ack or nack and then ack on the second attempt
483
+
readMessage(subscriberConn, false)
484
+
readMessage(subscriberConn, true)
485
+
486
+
// reading for another message should now timeout but give enough time for the ack delay to kick in
487
+
// should the second read of the message not have been ack'd properly
488
+
var topicLen uint64
489
+
_ = subscriberConn.SetReadDeadline(time.Now().Add(ackDelay + time.Millisecond*100))
490
+
err = binary.Read(subscriberConn, binary.BigEndian, &topicLen)
491
+
require.Error(t, err)
492
+
}
493
+
494
+
func TestSendsDataToTopicSubscriberDeliveryCountTooHighWithNoAck(t *testing.T) {
495
+
_ = createServer(t)
496
+
497
+
subscriberConn := createConnectionAndSubscribe(t, []string{topicA, topicB})
498
+
499
+
publisherConn, err := net.Dial("tcp", fmt.Sprintf("localhost%s", serverAddr))
500
+
require.NoError(t, err)
501
+
502
+
err = binary.Write(publisherConn, binary.BigEndian, Publish)
503
+
require.NoError(t, err)
504
+
505
+
topic := fmt.Sprintf("topic:%s", topicA)
506
+
messageData := "hello world"
507
+
508
+
// send topic first
509
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(topic)))
510
+
require.NoError(t, err)
511
+
_, err = publisherConn.Write([]byte(topic))
512
+
require.NoError(t, err)
513
+
514
+
// now send the data
515
+
err = binary.Write(publisherConn, binary.BigEndian, uint32(len(messageData)))
516
+
require.NoError(t, err)
517
+
n, err := publisherConn.Write([]byte(messageData))
518
+
require.NoError(t, err)
519
+
require.Equal(t, len(messageData), n)
520
+
521
+
// check the subsribers got the data
522
+
readMessage := func(conn net.Conn, ack bool) {
523
+
var topicLen uint64
524
+
err = binary.Read(conn, binary.BigEndian, &topicLen)
525
+
require.NoError(t, err)
526
+
527
+
topicBuf := make([]byte, topicLen)
528
+
_, err = conn.Read(topicBuf)
529
+
require.NoError(t, err)
530
+
assert.Equal(t, topicA, string(topicBuf))
531
+
532
+
var dataLen uint64
533
+
err = binary.Read(conn, binary.BigEndian, &dataLen)
534
+
require.NoError(t, err)
535
+
536
+
buf := make([]byte, dataLen)
537
+
n, err := conn.Read(buf)
538
+
require.NoError(t, err)
539
+
540
+
require.Equal(t, int(dataLen), n)
541
+
542
+
assert.Equal(t, messageData, string(buf))
543
+
544
+
if ack {
545
+
err = binary.Write(conn, binary.BigEndian, Ack)
546
+
require.NoError(t, err)
547
+
return
548
+
}
549
+
}
550
+
551
+
// nack the message 5 times
552
+
readMessage(subscriberConn, false)
553
+
readMessage(subscriberConn, false)
554
+
readMessage(subscriberConn, false)
555
+
readMessage(subscriberConn, false)
556
+
readMessage(subscriberConn, false)
557
+
558
+
// reading for the message should now timeout as we have nack'd the message too many times
559
+
var topicLen uint64
560
+
_ = subscriberConn.SetReadDeadline(time.Now().Add(ackDelay + time.Millisecond*100))
561
+
err = binary.Read(subscriberConn, binary.BigEndian, &topicLen)
562
+
require.Error(t, err)
563
+
}
+124
server/subscriber.go
+124
server/subscriber.go
···
1
+
package server
2
+
3
+
import (
4
+
"encoding/binary"
5
+
"fmt"
6
+
"log/slog"
7
+
"net"
8
+
"time"
9
+
10
+
"github.com/willdot/messagebroker/server/peer"
11
+
)
12
+
13
+
type subscriber struct {
14
+
peer *peer.Peer
15
+
topic string
16
+
messages chan message
17
+
18
+
ackDelay time.Duration
19
+
ackTimeout time.Duration
20
+
}
21
+
22
+
type message struct {
23
+
data []byte
24
+
deliveryCount int
25
+
}
26
+
27
+
func newMessage(data []byte) message {
28
+
return message{data: data, deliveryCount: 1}
29
+
}
30
+
31
+
func newSubscriber(peer *peer.Peer, topic string, ackDelay, ackTimeout time.Duration) *subscriber {
32
+
s := &subscriber{
33
+
peer: peer,
34
+
topic: topic,
35
+
messages: make(chan message),
36
+
ackDelay: ackDelay,
37
+
ackTimeout: ackTimeout,
38
+
}
39
+
40
+
go s.sendMessages()
41
+
42
+
return s
43
+
}
44
+
45
+
func (s *subscriber) sendMessages() {
46
+
// TODO: should think about how to break out of this if the subsciber closes its connection etc
47
+
for msg := range s.messages {
48
+
ack, err := s.sendMessage(s.topic, msg)
49
+
if err != nil {
50
+
slog.Error("failed to send to message", "error", err, "peer", s.peer.Addr())
51
+
}
52
+
53
+
if ack {
54
+
continue
55
+
}
56
+
57
+
if msg.deliveryCount >= 5 {
58
+
slog.Error("max delivery count for message. Dropping", "peer", s.peer.Addr())
59
+
continue
60
+
}
61
+
62
+
msg.deliveryCount++
63
+
s.addMessage(msg, s.ackDelay)
64
+
}
65
+
}
66
+
67
+
func (s *subscriber) addMessage(msg message, delay time.Duration) {
68
+
go func() {
69
+
time.Sleep(delay)
70
+
// TODO: should think about how to break out of this if the subsciber closes its connection etc
71
+
s.messages <- msg
72
+
}()
73
+
}
74
+
75
+
func (s *subscriber) sendMessage(topic string, msg message) (bool, error) {
76
+
var ack bool
77
+
op := func(conn net.Conn) error {
78
+
topicLen := uint64(len(topic))
79
+
err := binary.Write(conn, binary.BigEndian, topicLen)
80
+
if err != nil {
81
+
return fmt.Errorf("failed to send topic length: %w", err)
82
+
}
83
+
_, err = conn.Write([]byte(topic))
84
+
if err != nil {
85
+
return fmt.Errorf("failed to send topic: %w", err)
86
+
}
87
+
88
+
dataLen := uint64(len(msg.data))
89
+
90
+
err = binary.Write(conn, binary.BigEndian, dataLen)
91
+
if err != nil {
92
+
return fmt.Errorf("failed to send data length: %w", err)
93
+
}
94
+
95
+
_, err = conn.Write(msg.data)
96
+
if err != nil {
97
+
return fmt.Errorf("failed to write to peer: %w", err)
98
+
}
99
+
100
+
var ackRes Action
101
+
if err := conn.SetReadDeadline(time.Now().Add(s.ackTimeout)); err != nil {
102
+
slog.Error("failed to set connection read deadline", "error", err, "peer", s.peer.Addr())
103
+
}
104
+
defer func() {
105
+
if err := conn.SetReadDeadline(time.Time{}); err != nil {
106
+
slog.Error("failed to reset connection read deadline", "error", err, "peer", s.peer.Addr())
107
+
}
108
+
}()
109
+
err = binary.Read(conn, binary.BigEndian, &ackRes)
110
+
if err != nil {
111
+
return fmt.Errorf("failed to read ack from peer: %w", err)
112
+
}
113
+
114
+
if ackRes == Ack {
115
+
ack = true
116
+
}
117
+
118
+
return nil
119
+
}
120
+
121
+
err := s.peer.RunConnOperation(op)
122
+
123
+
return ack, err
124
+
}
+3
-57
server/topic.go
+3
-57
server/topic.go
···
1
1
package server
2
2
3
3
import (
4
-
"encoding/binary"
5
-
"fmt"
6
-
"log/slog"
7
4
"net"
8
5
"sync"
9
-
10
-
"github.com/willdot/messagebroker/server/peer"
11
6
)
12
7
13
8
type topic struct {
14
9
name string
15
-
subscriptions map[net.Addr]subscriber
10
+
subscriptions map[net.Addr]*subscriber
16
11
mu sync.Mutex
17
12
}
18
13
19
-
type subscriber struct {
20
-
peer *peer.Peer
21
-
currentOffset int
22
-
}
23
-
24
14
func newTopic(name string) *topic {
25
15
return &topic{
26
16
name: name,
27
-
subscriptions: make(map[net.Addr]subscriber),
17
+
subscriptions: make(map[net.Addr]*subscriber),
28
18
}
29
19
}
30
20
···
33
23
subscribers := t.subscriptions
34
24
t.mu.Unlock()
35
25
36
-
var wg sync.WaitGroup
37
-
38
26
for _, subscriber := range subscribers {
39
-
wg.Add(1)
40
-
sub := subscriber
41
-
go func() {
42
-
defer wg.Done()
43
-
sendMessage(sub, t.name, msgData)
44
-
}()
45
-
}
46
-
47
-
wg.Wait()
48
-
}
49
-
50
-
func sendMessage(sub subscriber, topicName string, message []byte) {
51
-
err := sub.peer.RunConnOperation(sendMessageOp(topicName, message))
52
-
if err != nil {
53
-
slog.Error("failed to send to message", "error", err, "peer", sub.peer.Addr())
54
-
return
55
-
}
56
-
}
57
-
58
-
func sendMessageOp(topic string, data []byte) peer.ConnOpp {
59
-
return func(conn net.Conn) error {
60
-
topicLen := uint64(len(topic))
61
-
err := binary.Write(conn, binary.BigEndian, topicLen)
62
-
if err != nil {
63
-
return fmt.Errorf("failed to send topic length: %w", err)
64
-
}
65
-
_, err = conn.Write([]byte(topic))
66
-
if err != nil {
67
-
return fmt.Errorf("failed to send topic: %w", err)
68
-
}
69
-
70
-
dataLen := uint64(len(data))
71
-
72
-
err = binary.Write(conn, binary.BigEndian, dataLen)
73
-
if err != nil {
74
-
return fmt.Errorf("failed to send data length: %w", err)
75
-
}
76
-
77
-
_, err = conn.Write(data)
78
-
if err != nil {
79
-
return fmt.Errorf("failed to write to peer: %w", err)
80
-
}
81
-
return nil
27
+
subscriber.addMessage(newMessage(msgData), 0)
82
28
}
83
29
}