Skip to content

Commit c7f0d32

Browse files
committed
fixes #224 Desire FastSendFailNoPeer option
1 parent 233273c commit c7f0d32

File tree

9 files changed

+237
-37
lines changed

9 files changed

+237
-37
lines changed

errors.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 The Mangos Authors
1+
// Copyright 2021 The Mangos Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use file except in compliance with the License.
@@ -43,4 +43,5 @@ const (
4343
ErrNotRaw = errors.ErrNotRaw
4444
ErrCanceled = errors.ErrCanceled
4545
ErrNoContext = errors.ErrNoContext
46+
ErrNoPeers = errors.ErrNoPeers
4647
)

errors/errors.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 The Mangos Authors
1+
// Copyright 2021 The Mangos Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use file except in compliance with the License.
@@ -49,4 +49,5 @@ const (
4949
ErrNotRaw = err("socket not raw")
5050
ErrCanceled = err("operation canceled")
5151
ErrNoContext = err("protocol does not support contexts")
52+
ErrNoPeers = err("no connected peers")
5253
)

options.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,15 @@ const (
214214
// Solaris platforms at present, and only when cgo support is enabled.
215215
// The value is an int.
216216
OptionPeerZone = "PEER-ZONE"
217+
218+
// OptionFailNoPeers causes send or receive operations to fail
219+
// immediately rather than waiting for a timeout if there are no
220+
// connected peers. This helps discriminate between cases involving
221+
// flow control, from those where we we have no peers. Use of this
222+
// option may make applications more brittle, as a temporary disconnect
223+
// that may otherwise self-heal quickly will now create an immediate
224+
// failure. Applications using this should be prepared to deal with
225+
// such failures. Note that not all protocols respect this -- best
226+
// effort protocols will particularly not support this.
227+
OptionFailNoPeers = "FAIL-NO-PEERS"
217228
)

protocol/protocol.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 The Mangos Authors
1+
// Copyright 2021 The Mangos Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use file except in compliance with the License.
@@ -67,6 +67,7 @@ const (
6767
ErrProtoOp = errors.ErrProtoOp
6868
ErrProtoState = errors.ErrProtoState
6969
ErrCanceled = errors.ErrCanceled
70+
ErrNoPeers = errors.ErrNoPeers
7071
)
7172

7273
// Common option definitions
@@ -84,6 +85,7 @@ const (
8485
OptionLinger = mangos.OptionLinger // Remove?
8586
OptionTTL = mangos.OptionTTL
8687
OptionBestEffort = mangos.OptionBestEffort
88+
OptionFailNoPeers = mangos.OptionFailNoPeers
8789
)
8890

8991
// MakeSocket creates a Socket on top of a Protocol.

protocol/push/push_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@ func TestPushOptions(t *testing.T) {
4949
func TestPushNoRecv(t *testing.T) {
5050
CannotRecv(t, NewSocket)
5151
}
52+
53+
func TestPushFastFailNoPeer(t *testing.T) {
54+
VerifyOptionBool(t, NewSocket, mangos.OptionFailNoPeers)
55+
56+
s := GetSocket(t, NewSocket)
57+
MustSucceed(t, s.SetOption(mangos.OptionFailNoPeers, true))
58+
MustBeError(t, s.Send([]byte("junk")), mangos.ErrNoPeers)
59+
}

protocol/req/req.go

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 The Mangos Authors
1+
// Copyright 2021 The Mangos Authors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use file except in compliance with the License.
@@ -40,23 +40,24 @@ type pipe struct {
4040
}
4141

4242
type context struct {
43-
s *socket
44-
cond *sync.Cond
45-
resendTime time.Duration // tunable resend time
46-
sendExpire time.Duration // how long to wait in send
47-
recvExpire time.Duration // how long to wait in recv
48-
sendTimer *time.Timer // send timer
49-
recvTimer *time.Timer // recv timer
50-
resender *time.Timer // resend timeout
51-
reqMsg *protocol.Message // message for transmit
52-
repMsg *protocol.Message // received reply
53-
sendMsg *protocol.Message // messaging waiting for send
54-
lastPipe *pipe // last pipe used for transmit
55-
reqID uint32 // request ID
56-
recvWait bool // true if a thread is blocked in RecvMsg
57-
bestEffort bool // if true, don't block waiting in send
58-
queued bool // true if we need to send a message
59-
closed bool // true if we are closed
43+
s *socket
44+
cond *sync.Cond
45+
resendTime time.Duration // tunable resend time
46+
sendExpire time.Duration // how long to wait in send
47+
recvExpire time.Duration // how long to wait in recv
48+
sendTimer *time.Timer // send timer
49+
recvTimer *time.Timer // recv timer
50+
resender *time.Timer // resend timeout
51+
reqMsg *protocol.Message // message for transmit
52+
repMsg *protocol.Message // received reply
53+
sendMsg *protocol.Message // messaging waiting for send
54+
lastPipe *pipe // last pipe used for transmit
55+
reqID uint32 // request ID
56+
recvWait bool // true if a thread is blocked in RecvMsg
57+
bestEffort bool // if true, don't block waiting in send
58+
failNoPeers bool // fast fail if no peers present
59+
queued bool // true if we need to send a message
60+
closed bool // true if we are closed
6061
}
6162

6263
type socket struct {
@@ -68,6 +69,7 @@ type socket struct {
6869
closed bool // true if we are closed
6970
sendq []*context // contexts waiting to send
7071
readyq []*pipe // pipes available for sending
72+
pipes map[uint32]*pipe // all pipes
7173
}
7274

7375
func (s *socket) send() {
@@ -101,7 +103,7 @@ func (s *socket) send() {
101103
}
102104
}
103105

104-
func (p *pipe) sendCtx(c *context, m *protocol.Message) {
106+
func (p *pipe) sendCtx(_ *context, m *protocol.Message) {
105107
s := p.s
106108

107109
// Send this message. If an error occurs, we examine the
@@ -245,6 +247,9 @@ func (c *context) SendMsg(m *protocol.Message) error {
245247
return protocol.ErrClosed
246248
}
247249

250+
if c.failNoPeers && len(s.pipes) == 0 {
251+
return protocol.ErrNoPeers
252+
}
248253
c.cancel() // this cancels any pending send or recv calls
249254
c.unscheduleSend()
250255

@@ -281,7 +286,7 @@ func (c *context) SendMsg(m *protocol.Message) error {
281286
// It is responsible for providing the blocking semantic and
282287
// ultimately back-pressure. Note that we will "continue" if
283288
// the send is canceled by a subsequent send.
284-
for c.sendMsg == m && !expired && !c.closed {
289+
for c.sendMsg == m && !expired && !c.closed && !(c.failNoPeers && len(s.pipes) == 0) {
285290
c.cond.Wait()
286291
}
287292
if c.sendMsg == m {
@@ -291,6 +296,9 @@ func (c *context) SendMsg(m *protocol.Message) error {
291296
if c.closed {
292297
return protocol.ErrClosed
293298
}
299+
if c.failNoPeers && len(s.pipes) == 0 {
300+
return protocol.ErrNoPeers
301+
}
294302
return protocol.ErrSendTimeout
295303
}
296304
return nil
@@ -303,6 +311,9 @@ func (c *context) RecvMsg() (*protocol.Message, error) {
303311
if s.closed || c.closed {
304312
return nil, protocol.ErrClosed
305313
}
314+
if c.failNoPeers && len(s.pipes) == 0 {
315+
return nil, protocol.ErrNoPeers
316+
}
306317
if c.recvWait || c.reqID == 0 {
307318
return nil, protocol.ErrProtoState
308319
}
@@ -332,11 +343,14 @@ func (c *context) RecvMsg() (*protocol.Message, error) {
332343
c.cond.Broadcast()
333344

334345
if m == nil {
346+
if c.closed {
347+
return nil, protocol.ErrClosed
348+
}
335349
if expired {
336350
return nil, protocol.ErrRecvTimeout
337351
}
338-
if c.closed {
339-
return nil, protocol.ErrClosed
352+
if c.failNoPeers && len(s.pipes) == 0 {
353+
return nil, protocol.ErrNoPeers
340354
}
341355
return nil, protocol.ErrCanceled
342356
}
@@ -380,6 +394,16 @@ func (c *context) SetOption(name string, value interface{}) error {
380394
return nil
381395
}
382396
return protocol.ErrBadValue
397+
398+
case protocol.OptionFailNoPeers:
399+
if v, ok := value.(bool); ok {
400+
c.s.Lock()
401+
c.failNoPeers = v
402+
c.s.Unlock()
403+
return nil
404+
}
405+
return protocol.ErrBadValue
406+
383407
}
384408

385409
return protocol.ErrBadOption
@@ -407,6 +431,11 @@ func (c *context) GetOption(option string) (interface{}, error) {
407431
v := c.bestEffort
408432
c.s.Unlock()
409433
return v, nil
434+
case protocol.OptionFailNoPeers:
435+
c.s.Lock()
436+
v := c.failNoPeers
437+
c.s.Unlock()
438+
return v, nil
410439
}
411440

412441
return nil, protocol.ErrBadOption
@@ -493,6 +522,7 @@ func (s *socket) AddPipe(pp protocol.Pipe) error {
493522
}
494523
s.readyq = append(s.readyq, p)
495524
s.send()
525+
s.pipes[pp.ID()] = p
496526
go p.receiver()
497527
return nil
498528
}
@@ -506,8 +536,11 @@ func (s *socket) RemovePipe(pp protocol.Pipe) {
506536
s.readyq = append(s.readyq[:i], s.readyq[i+1:]...)
507537
}
508538
}
539+
delete(s.pipes, pp.ID())
509540
for c := range s.ctxs {
510-
if c.lastPipe == p && c.reqMsg != nil {
541+
if c.failNoPeers && len(s.pipes) == 0 {
542+
c.cancel()
543+
} else if c.lastPipe == p && c.reqMsg != nil {
511544
// We are closing this pipe, so we need to
512545
// immediately reschedule it.
513546
c.lastPipe = nil
@@ -539,6 +572,7 @@ func NewProtocol() protocol.Protocol {
539572
nextID: uint32(time.Now().UnixNano()), // quasi-random
540573
ctxs: make(map[*context]struct{}),
541574
ctxByID: make(map[uint32]*context),
575+
pipes: make(map[uint32]*pipe),
542576
}
543577
s.defCtx = &context{
544578
s: s,

protocol/req/req_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func TestReqOptions(t *testing.T) {
4545
VerifyOptionDuration(t, NewSocket, mangos.OptionSendDeadline)
4646
VerifyOptionDuration(t, NewSocket, mangos.OptionRetryTime)
4747
VerifyOptionBool(t, NewSocket, mangos.OptionBestEffort)
48+
VerifyOptionBool(t, NewSocket, mangos.OptionFailNoPeers)
4849
}
4950

5051
func TestReqClosed(t *testing.T) {
@@ -450,3 +451,89 @@ func TestReqMultiContexts(t *testing.T) {
450451
MustBeTrue(t, recv[i] == repeat)
451452
}
452453
}
454+
455+
func TestReqSendNoPeers(t *testing.T) {
456+
s := GetSocket(t, NewSocket)
457+
MustSucceed(t, s.SetOption(mangos.OptionFailNoPeers, true))
458+
MustBeError(t, s.Send([]byte("junk")), mangos.ErrNoPeers)
459+
MustSucceed(t, s.Close())
460+
}
461+
462+
func TestReqSendNoPeerDisconnect(t *testing.T) {
463+
VerifyOptionBool(t, NewSocket, mangos.OptionFailNoPeers)
464+
465+
s := GetSocket(t, NewSocket)
466+
p := GetSocket(t, rep.NewSocket)
467+
c1, err := s.OpenContext()
468+
MustSucceed(t, err)
469+
c2, err := s.OpenContext()
470+
MustSucceed(t, err)
471+
MustSucceed(t, s.SetOption(mangos.OptionSendDeadline, time.Second))
472+
473+
MustSucceed(t, s.SetOption(mangos.OptionFailNoPeers, true))
474+
475+
// Now connect them so they can drain -- we should only have 3 messages
476+
// that arrive at the peer.
477+
ConnectPair(t, s, p)
478+
time.Sleep(time.Millisecond * 20)
479+
// this logic fills the queue
480+
go func() {
481+
_ = c1.Send([]byte("one"))
482+
}()
483+
go func() {
484+
_ = c2.Send([]byte("one"))
485+
}()
486+
time.Sleep(time.Millisecond * 10)
487+
go func() {
488+
time.Sleep(time.Millisecond * 20)
489+
MustSucceed(t, p.Close())
490+
}()
491+
time.Sleep(time.Millisecond * 20)
492+
493+
MustBeError(t, s.Send([]byte("three")), mangos.ErrNoPeers)
494+
MustSucceed(t, s.Close())
495+
}
496+
497+
498+
func TestReqRecvNoPeer(t *testing.T) {
499+
VerifyOptionBool(t, NewSocket, mangos.OptionFailNoPeers)
500+
501+
s := GetSocket(t, NewSocket)
502+
p := GetSocket(t, rep.NewSocket)
503+
MustSucceed(t, s.SetOption(mangos.OptionSendDeadline, time.Second))
504+
505+
MustSucceed(t, s.SetOption(mangos.OptionFailNoPeers, true))
506+
507+
// Now connect them so they can drain -- we should only have 3 messages
508+
// that arrive at the peer.
509+
ConnectPair(t, s, p)
510+
time.Sleep(time.Millisecond * 20)
511+
MustSucceed(t, s.Send([]byte("one"))) // sent by the socket, picked up by rep socket
512+
time.Sleep(time.Millisecond * 20)
513+
MustSucceed(t, p.Close())
514+
time.Sleep(time.Millisecond*20)
515+
MustNotRecv(t, s, mangos.ErrNoPeers)
516+
MustSucceed(t, s.Close())
517+
}
518+
519+
func TestReqRecvNoPeerDisconnect(t *testing.T) {
520+
VerifyOptionBool(t, NewSocket, mangos.OptionFailNoPeers)
521+
522+
s := GetSocket(t, NewSocket)
523+
p := GetSocket(t, rep.NewSocket)
524+
MustSucceed(t, s.SetOption(mangos.OptionSendDeadline, time.Second))
525+
526+
MustSucceed(t, s.SetOption(mangos.OptionFailNoPeers, true))
527+
528+
// Now connect them so they can drain -- we should only have 3 messages
529+
// that arrive at the peer.
530+
ConnectPair(t, s, p)
531+
time.Sleep(time.Millisecond * 20)
532+
MustSucceed(t, s.Send([]byte("one"))) // sent by the socket, picked up by rep socket
533+
go func() {
534+
time.Sleep(time.Millisecond*10)
535+
MustSucceed(t, p.Close())
536+
}()
537+
MustNotRecv(t, s, mangos.ErrNoPeers)
538+
MustSucceed(t, s.Close())
539+
}

0 commit comments

Comments
 (0)