Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
neoppod
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Levin Zimmermann
neoppod
Commits
3ec038b7
Commit
3ec038b7
authored
Nov 30, 2016
by
Kirill Smelkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
71843e90
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
225 additions
and
51 deletions
+225
-51
t/neo/connection.go
t/neo/connection.go
+155
-51
t/neo/connection_test.go
t/neo/connection_test.go
+70
-0
No files found.
t/neo/connection.go
View file @
3ec038b7
// TODO copyright / license
// Copyright (C) 2016 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 2, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
//
TODO tex
t
//
NEO | Connection managemen
t
package
neo
package
neo
...
@@ -8,78 +18,127 @@ import (
...
@@ -8,78 +18,127 @@ import (
"net"
"net"
)
)
// NodeLink is a node-node connection in NEO
// NodeLink is a node-node link in NEO
// A node-node connection represents bidirectional symmetrical communication
// link in between 2 NEO nodes. The link provides service for packets
// exchange and for multiplexing several higher-level communication channels on
// top of node-node link.
//
// All packets are classified to be of one of the following kind:
// - notify: a packet is sent without expecting any reply
// - ask: a packet is sent and reply is expected
// - answer: a packet replying to previous ask
//
// At any time there can be several Asks packets issued by both nodes.
// For an Ask packet a single Answer reply is expected XXX vs protocol where there is one request and list of replies ?
//
// XXX -> multiple subconnection explicitly closed with ability to chat
// multiple packets without spawning goroutines? And only single answer
// expected implemented that after only ask-send / answer-receive the
// (sub-)connection is explicitly closed ?
//
// XXX it is maybe better to try to avoid multiplexing by hand and let the OS do it?
//
//
// A
reply to particular Ask packet, once received, will be delivered to
// A
node-node link represents bidirectional symmetrical communication
// c
orresponding goroutine which originally issued Ask XXX this can be put into interface
// c
hannel in between 2 NEO nodes. The link provides service for packets
//
//
exchange and for multiplexing several communication connections on
//
TODO text
//
top of the node-node link.
//
//
// TODO goroutine guarantees
// New connection can be created with .NewConn() . Once connection is
// created and data is sent over it, on peer's side another corresponding
// new connection will be created - accepting first packet "request" - and all
// further communication send/receive exchange will be happenning in between
// those 2 connections. TODO conn close
//
//
// For a node to be able to accept new incoming connection it has to register
// corresponding handler with .HandleNewConn() . Without such handler
// registered the node will be able to only initiate new connections, not
// accept new ones from its peer.
//
//
// TODO NodeLink close
//
//
//
XXX naming (-> Conn ?, PeerLink ?)
//
It is safe to use NodeLink from multiple goroutines simultaneously.
type
NodeLink
struct
{
type
NodeLink
struct
{
// XXX naming (-> PeerLink ?)
peerLink
net
.
Conn
// raw conn to peer
peerLink
net
.
Conn
// raw conn to peer
connTab
map
[
uint32
]
XXX
// msgid -> connection associated with msgid
// TODO locking
connTab
map
[
uint32
]
*
Conn
// msgid -> connection associated with msgid
handleNewConn
func
(
conn
*
Conn
)
// handler for new connections XXX -> ConnHandler (a-la Handler in net/http ?)
// TODO peerLink .LocalAddr() vs .RemoteAddr() -> msgid even/odd ? (XXX vs NAT ?)
}
}
// Conn is a sub-connection established over NodeLink
// Conn is a connection established over NodeLink
// TODO text
//
// Data can be sent and received over it.
// Once connection is no longer needed it has to be closed.
//
// TODO goroutine guarantee (looks to be safe, but if not check whether we need it)
type
Conn
struct
{
type
Conn
struct
{
nodeLink
*
NodeLink
rxq
chan
Pkt
// XXX chan &Pkt ?
rxq
chan
Pkt
// XXX chan &Pkt ?
}
}
// Send notify packet to peer
func
(
c
*
NodeLink
)
Notify
(
pkt
XXX
)
error
{
// Send packet via connection
// TODO
// XXX vs cancel
func
(
Conn
*
c
)
Send
(
pkt
Pkt
)
error
{
pkt
.
MsgId
=
...
// TODO next msgid, or using same msgid as received
_
,
err
:=
c
.
nodeLink
.
peerLink
.
Write
(
pkt
.
WholeBuffer
())
// TODO -> sendPkt(pkt)
if
err
!=
nil
{
// TODO data could be written partially and thus the message stream is now broken
// -> close connection / whole NodeLink ?
}
return
err
}
// Receive packet from connection
// XXX vs cancel
func
(
Conn
*
c
)
Recv
()
(
PktBuf
,
error
)
{
pkt
,
ok
<-
rxq
if
!
ok
{
return
PktBuf
{},
io
.
EOF
// XXX check erroring & other errors?
}
return
pkt
,
nil
}
// Close connection
// Any blocked Send() or Recv() will be unblocked and return error
// XXX vs cancel
func
(
Conn
*
c
)
Close
()
error
{
// XXX do we need error here?
// TODO adjust c.nodeLink.connTab + more ?
// TODO interrupt Send/Recv
panic
(
"TODO Conn.Close"
)
}
// Make a new NodeLink from already established net.Conn
func
NewNodeLink
(
c
net
.
Conn
)
*
NodeLink
{
nl
:=
NodeLink
{
peerLink
:
c
,
connTab
:
{},
//make(map[uint32]*Conn),
}
// XXX run serveRecv() in a goroutine here?
return
&
nl
}
}
// Send packet and wait for replied answer packet
// Make a connection on top of node-node link
func
(
c
*
NodeLink
)
Ask
(
pkt
XXX
)
(
answer
Pkt
,
err
error
)
{
func
(
nl
*
NodeLink
)
NewConn
()
*
Conn
{
// TODO
c
:=
&
Conn
{
nodeLink
:
nl
,
rxq
:
make
(
chan
Pkt
)}
// XXX locking
nl
.
connTab
[
...
]
=
c
// XXX also check not a duplicate
return
c
}
}
// TODO how handle receive
// -> TODO Answer
// handle incoming packets routing them to either appropriate
// ServeRecv handles incoming packets routing them to either appropriate
// already-established connections or to new serving goroutine
// already-established connection or to new serving goroutine.
func
(
c
*
NodeLink
)
serveReceive
()
error
{
// TODO vs cancel
// XXX someone has to run serveRecv in a goroutine - XXX user or we internally ?
func
(
nl
*
NodeLink
)
serveRecv
()
error
{
for
{
for
{
// receive 1 packet
// receive 1 packet
pkt
,
err
:=
c
.
recvPkt
()
pkt
,
err
:=
nl
.
recvPkt
()
if
err
!=
nil
{
if
err
!=
nil
{
panic
(
err
)
// XXX err
panic
(
err
)
// XXX err
}
}
// if we don't yet have connection established for pkt.Id spawn connection-serving goroutine
// if we don't yet have connection established for pkt.Id spawn connection-serving goroutine
// XXX connTab locking
// XXX connTab locking
// TODO also check for conn ready to handle new packets, e.g. marked with msgid = 0
conn
:=
nl
.
connTab
[
pkt
.
Id
]
// (to avoid spawning goroutine each time for new packet)
if
conn
==
nil
{
conn
:=
c
.
connTab
[
pkt
.
Id
]
if
nl
.
handleNewConn
==
nil
{
if
conn
!=
nil
{
// we are not accepting incoming connections - ignore packet
go
...
// XXX also log?
continue
}
conn
=
nl
.
NewConn
(
...
)
// XXX should also update connTab
// TODO avoid spawning goroutine for each new Ask request -
// - by keeping pool of read inactive goroutine / conn pool
go
nl
.
handleNewConn
(
conn
)
}
}
// route packet to serving goroutine handler
// route packet to serving goroutine handler
...
@@ -87,9 +146,16 @@ func (c *NodeLink) serveReceive() error {
...
@@ -87,9 +146,16 @@ func (c *NodeLink) serveReceive() error {
}
}
}
}
// Set handler for new incoming connections
func
(
nl
*
NodeLink
)
HandleNewConn
(
h
func
(
*
Conn
))
{
// XXX locking
nl
.
handleNewConn
=
h
// NOTE can change handler at runtime XXX do we need this?
}
// information about (received ?) packet
// information about (received ?) packet
// XXX place?
// XXX place?
type
Pkt
struct
{
type
Pkt
Buf
struct
{
PktHead
PktHead
Body
[]
byte
Body
[]
byte
}
}
...
@@ -131,3 +197,41 @@ func (c *NodeLink) recvPkt() (pkt Pkt, err error) {
...
@@ -131,3 +197,41 @@ func (c *NodeLink) recvPkt() (pkt Pkt, err error) {
return
pkt
,
nil
return
pkt
,
nil
}
}
// ----------------------------------------
// XXX ^^^ original description about notify/ask/answer
// All packets are classified to be of one of the following kind:
// - notify: a packet is sent without expecting any reply
// - ask: a packet is sent and reply is expected
// - answer: a packet replying to previous ask
//
// At any time there can be several Asks packets issued by both nodes.
// For an Ask packet a single Answer reply is expected XXX vs protocol where there is one request and list of replies ?
//
// XXX -> multiple subconnection explicitly closed with ability to chat
// multiple packets without spawning goroutines? And only single answer
// expected implemented that after only ask-send / answer-receive the
// (sub-)connection is explicitly closed ?
//
// XXX it is maybe better to try to avoid multiplexing by hand and let the OS do it?
//
// A reply to particular Ask packet, once received, will be delivered to
// corresponding goroutine which originally issued Ask XXX this can be put into interface
// // Send notify packet to peer
// func (c *NodeLink) Notify(pkt XXX) error {
// // TODO
// }
//
// // Send packet and wait for replied answer packet
// func (c *NodeLink) Ask(pkt XXX) (answer Pkt, err error) {
// // TODO
// }
t/neo/connection_test.go
0 → 100644
View file @
3ec038b7
// Copyright (C) 2016 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 2, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// NEO | Connection management. Tests
package
neo
import
(
"net"
"testing"
)
func
xsend
(
c
*
Conn
,
pkt
PktBuf
)
{
err
:=
c
.
Send
(
pkt
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
func
xrecv
(
c
*
Conn
)
PktBuf
{
pkt
,
err
:=
c
.
Recv
()
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
return
pkt
}
func
TestNodeLink
(
t
*
testing
.
T
)
{
// TODO verify NodeLink via net.Pipe
node1
,
node2
:=
net
.
Pipe
()
nl1
:=
NewNodeLink
(
node1
)
nl2
:=
NewNodeLink
(
node2
)
// first check raw exchange works
go
func
()
{
err
:=
nl1
.
sendPkt
(
...
)
if
err
!=
nil
{
t
.
Fatal
(
...
)
}
}()
pkt
,
err
:=
nl2
.
recvPkt
(
...
)
if
err
!=
nil
{
t
.
Fatal
(
...
)
}
// TODO check pkt == what was sent
// TODO also check ^^^ in opposite direction
// test 1 channels on top of nodelink
c1
:=
nl1
.
NewConn
()
nl2
.
HandleNewConn
(
func
(
c
*
Conn
)
{
pkt
:=
xrecv
(
c
)
// XXX t.Fatal() must be called from main goroutine -> context.Cancel ?
// change pkt a bit (TODO) and send it back
err
=
c
.
Send
(
pkt
)
// XXX err
c
.
Close
()
// XXX err
})
c1
.
Send
(
pkt
)
// XXX err
pkt2
:=
c1
.
Recv
()
// XXX err
// TODO check pkt2 is pkt1 + small modification
// test 2 channels with replies comming in reversed time order
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment