├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── examples ├── .gitignore ├── Makefile └── gonode.go ├── gen_server.go ├── global_name_server.go ├── nconst.go ├── net_kernel.go ├── node.go └── rpc.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license. 2 | 3 | Copyright (c) 2012-2013 Metachord Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 17 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all clean: 2 | (cd ../../../ && ${MAKE} $@) 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-node # 2 | 3 | Implementation of Erlang/OTP node in Go 4 | 5 | Supported features: 6 | 7 | * Publish listen port via EPMD 8 | * Handle incoming connection from other node using Erlang Distribution Protocol 9 | * Spawn Erlang-like processes 10 | * Register and unregister processes with simple atom 11 | * Send messages to registered (using atom) or not registered (using Pid) processes at Go-node or remote Erlang-node 12 | * Create own process with `GenServer` behaviour (like `gen_server` in Erlang/OTP) 13 | 14 | Not supported (but should be): 15 | 16 | * Initiate connection to other node 17 | * Supervisors tree 18 | * Create own behaviours 19 | * RPC callbacks 20 | * Atom cache references to increase throughput 21 | 22 | ## Examples ## 23 | 24 | See `examples/` to see example of go-node and `GenServer` process 25 | 26 | Another project which uses this library: Eclus https://github.com/goerlang/eclus 27 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /src 3 | /gonode 4 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH := $(shell pwd) 2 | 3 | all: gonode 4 | 5 | gonode: 6 | GOPATH=$(GOPATH) go get -d 7 | GOPATH=$(GOPATH) go build -o $@ 8 | 9 | clean: 10 | GOPATH=$(GOPATH) go clean 11 | ${RM} -r pkg/ src/ 12 | 13 | .PHONY: gonode 14 | -------------------------------------------------------------------------------- /examples/gonode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/goerlang/etf" 5 | "github.com/goerlang/node" 6 | "log" 7 | ) 8 | 9 | // GenServer implementation structure 10 | type gonodeSrv struct { 11 | node.GenServerImpl 12 | completeChan chan bool 13 | } 14 | 15 | // Init initializes process state using arbitrary arguments 16 | func (gs *gonodeSrv) Init(args ...interface{}) { 17 | log.Printf("GO_SRV: Init: %#v", args) 18 | 19 | // Self-registration with name go_srv 20 | gs.Node.Register(etf.Atom("go_srv"), gs.Self) 21 | 22 | // Store first argument as channel 23 | gs.completeChan = args[0].(chan bool) 24 | } 25 | 26 | // HandleCast handles incoming messages from `gen_server:cast/2` 27 | // Call `gen_server:cast({go_srv, gonode@localhost}, stop)` at Erlang node to stop this Go-node 28 | func (gs *gonodeSrv) HandleCast(message *etf.Term) { 29 | log.Printf("GO_SRV: HandleCast: %#v", *message) 30 | 31 | // Check type of message 32 | switch t := (*message).(type) { 33 | case etf.Atom: 34 | // If message is atom 'stop', we should say it to main process 35 | if string(t) == "stop" { 36 | gs.completeChan <- true 37 | } 38 | } 39 | } 40 | 41 | // HandleCall handles incoming messages from `gen_server:call/2`, if returns non-nil term, 42 | // then calling process have reply 43 | // Call `gen_server:call({go_srv, gonode@localhost}, Message)` at Erlang node 44 | func (gs *gonodeSrv) HandleCall(message *etf.Term, from *etf.Tuple) (reply *etf.Term) { 45 | log.Printf("GO_SRV: HandleCall: %#v, From: %#v", *message, *from) 46 | 47 | // Just create new term tuple where first element is atom 'ok', second 'go_reply' and third is original message 48 | replyTerm := etf.Term(etf.Tuple{etf.Atom("ok"), etf.Atom("go_reply"), *message}) 49 | reply = &replyTerm 50 | return 51 | } 52 | 53 | // HandleInfo handles all another incoming messages 54 | func (gs *gonodeSrv) HandleInfo(message *etf.Term) { 55 | log.Printf("GO_SRV: HandleInfo: %#v", *message) 56 | } 57 | 58 | // Terminate called when process died 59 | func (gs *gonodeSrv) Terminate(reason interface{}) { 60 | log.Printf("GO_SRV: Terminate: %#v", reason.(int)) 61 | } 62 | 63 | func main() { 64 | // Initialize new node with given name and cookie 65 | enode := node.NewNode("gonode@localhost", "123") 66 | 67 | // Allow node be available on 5588 port 68 | err := enode.Publish(5588) 69 | if err != nil { 70 | log.Fatalf("Cannot publish: %s", err) 71 | } 72 | 73 | // Create channel to receive message when main process should be stopped 74 | completeChan := make(chan bool) 75 | 76 | // Initialize new instance of gonodeSrv structure which implements Process behaviour 77 | eSrv := new(gonodeSrv) 78 | 79 | // Spawn process with one arguments 80 | enode.Spawn(eSrv, completeChan) 81 | 82 | // RPC 83 | // Create closure 84 | eClos := func(terms etf.List) (r etf.Term) { 85 | r = etf.Term(etf.Tuple{etf.Atom("gonode"), etf.Atom("reply"), len(terms)}) 86 | return 87 | } 88 | 89 | // Provide it to call via RPC with `rpc:call(gonode@localhost, go_rpc, call, [as, qwe])` 90 | err = enode.RpcProvide("go_rpc", "call", eClos) 91 | if err != nil { 92 | log.Printf("Cannot provide function to RPC: %s", err) 93 | } 94 | 95 | 96 | // Wait to stop 97 | <-completeChan 98 | 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /gen_server.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/goerlang/etf" 5 | "log" 6 | ) 7 | 8 | // GenServer interface 9 | type GenServer interface { 10 | Init(args ...interface{}) 11 | HandleCast(message *etf.Term) 12 | HandleCall(message *etf.Term, from *etf.Tuple) (reply *etf.Term) 13 | HandleInfo(message *etf.Term) 14 | Terminate(reason interface{}) 15 | } 16 | 17 | // GenServerImpl is implementation of GenServer interface 18 | type GenServerImpl struct { 19 | Node *Node // current node of process 20 | Self etf.Pid // Pid of process 21 | } 22 | 23 | // Options returns map of default process-related options 24 | func (gs *GenServerImpl) Options() map[string]interface{} { 25 | return map[string]interface{}{ 26 | "chan-size": 100, // size of channel for regular messages 27 | "ctl-chan-size": 100, // size of channel for control messages 28 | } 29 | } 30 | 31 | // ProcessLoop executes during whole time of process life. 32 | // It receives incoming messages from channels and handle it using methods of behaviour implementation 33 | func (gs *GenServerImpl) ProcessLoop(pcs procChannels, pd Process, args ...interface{}) { 34 | pd.(GenServer).Init(args...) 35 | pcs.init <- true 36 | defer func() { 37 | if r := recover(); r != nil { 38 | // TODO: send message to parent process 39 | log.Printf("GenServer recovered: %#v", r) 40 | } 41 | }() 42 | for { 43 | var message etf.Term 44 | var fromPid etf.Pid 45 | select { 46 | case msg := <-pcs.in: 47 | message = msg 48 | case msgFrom := <-pcs.inFrom: 49 | message = msgFrom[1] 50 | fromPid = msgFrom[0].(etf.Pid) 51 | case ctlMsg := <-pcs.ctl: 52 | switch m := ctlMsg.(type) { 53 | case etf.Tuple: 54 | switch mtag := m[0].(type) { 55 | case etf.Atom: 56 | switch mtag { 57 | case etf.Atom("$go_ctl"): 58 | nLog("Control message: %#v", m) 59 | default: 60 | nLog("Unknown message: %#v", m) 61 | } 62 | default: 63 | nLog("Unknown message: %#v", m) 64 | } 65 | default: 66 | nLog("Unknown message: %#v", m) 67 | } 68 | continue 69 | } 70 | nLog("Message from %#v", fromPid) 71 | switch m := message.(type) { 72 | case etf.Tuple: 73 | switch mtag := m[0].(type) { 74 | case etf.Atom: 75 | switch mtag { 76 | case etf.Atom("$go_ctl"): 77 | nLog("Control message: %#v", message) 78 | case etf.Atom("$gen_call"): 79 | fromTuple := m[1].(etf.Tuple) 80 | reply := pd.(GenServer).HandleCall(&m[2], &fromTuple) 81 | if reply != nil { 82 | gs.Reply(&fromTuple, reply) 83 | } 84 | case etf.Atom("$gen_cast"): 85 | pd.(GenServer).HandleCast(&m[1]) 86 | default: 87 | pd.(GenServer).HandleInfo(&message) 88 | } 89 | default: 90 | nLog("mtag: %#v", mtag) 91 | pd.(GenServer).HandleInfo(&message) 92 | } 93 | default: 94 | nLog("m: %#v", m) 95 | pd.(GenServer).HandleInfo(&message) 96 | } 97 | } 98 | } 99 | 100 | // Reply sends delayed reply at incoming `gen_server:call/2` 101 | func (gs *GenServerImpl) Reply(fromTuple *etf.Tuple, reply *etf.Term) { 102 | gs.Node.Send((*fromTuple)[0].(etf.Pid), etf.Tuple{(*fromTuple)[1], *reply}) 103 | } 104 | 105 | func (gs *GenServerImpl) setNode(node *Node) { 106 | gs.Node = node 107 | } 108 | 109 | func (gs *GenServerImpl) setPid(pid etf.Pid) { 110 | gs.Self = pid 111 | } 112 | -------------------------------------------------------------------------------- /global_name_server.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/goerlang/etf" 5 | ) 6 | 7 | type globalNameServer struct { 8 | GenServerImpl 9 | } 10 | 11 | func (gns *globalNameServer) Init(args ...interface{}) { 12 | nLog("GLOBAL_NAME_SERVER: Init: %#v", args) 13 | gns.Node.Register(etf.Atom("global_name_server"), gns.Self) 14 | } 15 | 16 | func (gns *globalNameServer) HandleCast(message *etf.Term) { 17 | nLog("GLOBAL_NAME_SERVER: HandleCast: %#v", *message) 18 | } 19 | 20 | func (gns *globalNameServer) HandleCall(message *etf.Term, from *etf.Tuple) (reply *etf.Term) { 21 | nLog("GLOBAL_NAME_SERVER: HandleCall: %#v, From: %#v", *message, *from) 22 | replyTerm := etf.Term(etf.Atom("reply")) 23 | reply = &replyTerm 24 | return 25 | } 26 | 27 | func (gns *globalNameServer) HandleInfo(message *etf.Term) { 28 | nLog("GLOBAL_NAME_SERVER: HandleInfo: %#v", *message) 29 | } 30 | 31 | func (gns *globalNameServer) Terminate(reason interface{}) { 32 | nLog("GLOBAL_NAME_SERVER: Terminate: %#v", reason.(int)) 33 | } 34 | -------------------------------------------------------------------------------- /nconst.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | // Distributed operations codes (http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html) 4 | const ( 5 | LINK = 1 6 | SEND = 2 7 | EXIT = 3 8 | UNLINK = 4 9 | NODE_LINK = 5 10 | REG_SEND = 6 11 | GROUP_LEADER = 7 12 | EXIT2 = 8 13 | SEND_TT = 12 14 | EXIT_TT = 13 15 | REG_SEND_TT = 16 16 | EXIT2_TT = 18 17 | MONITOR_P = 19 18 | DEMONITOR_P = 20 19 | MONITOR_P_EXIT = 21 20 | ) 21 | -------------------------------------------------------------------------------- /net_kernel.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/goerlang/etf" 5 | ) 6 | 7 | type netKernel struct { 8 | GenServerImpl 9 | } 10 | 11 | func (nk *netKernel) Init(args ...interface{}) { 12 | nLog("NET_KERNEL: Init: %#v", args) 13 | nk.Node.Register(etf.Atom("net_kernel"), nk.Self) 14 | } 15 | 16 | func (nk *netKernel) HandleCast(message *etf.Term) { 17 | nLog("NET_KERNEL: HandleCast: %#v", *message) 18 | } 19 | 20 | func (nk *netKernel) HandleCall(message *etf.Term, from *etf.Tuple) (reply *etf.Term) { 21 | nLog("NET_KERNEL: HandleCall: %#v, From: %#v", *message, *from) 22 | switch t := (*message).(type) { 23 | case etf.Tuple: 24 | if len(t) == 2 { 25 | switch tag := t[0].(type) { 26 | case etf.Atom: 27 | if string(tag) == "is_auth" { 28 | nLog("NET_KERNEL: is_auth: %#v", t[1]) 29 | replyTerm := etf.Term(etf.Atom("yes")) 30 | reply = &replyTerm 31 | } 32 | } 33 | } 34 | } 35 | return 36 | } 37 | 38 | func (nk *netKernel) HandleInfo(message *etf.Term) { 39 | nLog("NET_KERNEL: HandleInfo: %#v", *message) 40 | } 41 | 42 | func (nk *netKernel) Terminate(reason interface{}) { 43 | nLog("NET_KERNEL: Terminate: %#v", reason.(int)) 44 | } 45 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2013 Metachord Ltd. 2 | // All rights reserved. 3 | // Use of this source code is governed by a MIT license 4 | // that can be found in the LICENSE file. 5 | 6 | /* 7 | Package node provides interface for creation and publishing node using 8 | Erlang distribution protocol: http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html 9 | 10 | The Publish function allows incoming connection to the current node on 5858 port. 11 | EPMD will reply with this port on name request for this node name 12 | 13 | enode := node.NewNode(name, cookie) 14 | err := enode.Publish(5858) 15 | if err != nil { 16 | log.Printf("Cannot publish: %s", err) 17 | enode = nil 18 | } 19 | 20 | 21 | Function Spawn creates new process from struct which implements Process interface: 22 | 23 | eSrv := new(eclusSrv) 24 | pid := enode.Spawn(eSrv) 25 | 26 | Now you can call Register function to store this pid with arbitrary name: 27 | 28 | enode.Register(etf.Atom("eclus"), pid) 29 | 30 | */ 31 | package node 32 | 33 | import ( 34 | "encoding/binary" 35 | "flag" 36 | "fmt" 37 | "github.com/goerlang/dist" 38 | "github.com/goerlang/epmd" 39 | "github.com/goerlang/etf" 40 | "log" 41 | "net" 42 | "strconv" 43 | "strings" 44 | ) 45 | 46 | var nTrace bool 47 | 48 | func init() { 49 | flag.BoolVar(&nTrace, "erlang.node.trace", false, "trace erlang node") 50 | } 51 | 52 | func nLog(f string, a ...interface{}) { 53 | if nTrace { 54 | log.Printf(f, a...) 55 | } 56 | } 57 | 58 | type regReq struct { 59 | replyTo chan etf.Pid 60 | channels procChannels 61 | } 62 | 63 | type regNameReq struct { 64 | name etf.Atom 65 | pid etf.Pid 66 | } 67 | 68 | type unregNameReq struct { 69 | name etf.Atom 70 | } 71 | 72 | type registryChan struct { 73 | storeChan chan regReq 74 | regNameChan chan regNameReq 75 | unregNameChan chan unregNameReq 76 | } 77 | 78 | type nodeConn struct { 79 | conn net.Conn 80 | wchan chan []etf.Term 81 | } 82 | 83 | type systemProcs struct { 84 | netKernel *netKernel 85 | globalNameServer *globalNameServer 86 | rpcRex *rpcRex 87 | } 88 | 89 | type Node struct { 90 | epmd.NodeInfo 91 | Cookie string 92 | port int32 93 | registry *registryChan 94 | channels map[etf.Pid]procChannels 95 | registered map[etf.Atom]etf.Pid 96 | neighbors map[etf.Atom]nodeConn 97 | sysProcs systemProcs 98 | } 99 | 100 | type procChannels struct { 101 | in chan etf.Term 102 | inFrom chan etf.Tuple 103 | ctl chan etf.Term 104 | init chan bool 105 | } 106 | 107 | // Behaviour interface contains methods you should implement to make own process behaviour 108 | type Behaviour interface { 109 | ProcessLoop(pcs procChannels, pd Process, args ...interface{}) // method which implements control flow of process 110 | } 111 | 112 | // Process interface contains methods which should be implemented in each process 113 | type Process interface { 114 | Options() (options map[string]interface{}) // method returns process-related options 115 | setNode(node *Node) // method set pointer to Node structure 116 | setPid(pid etf.Pid) // method set pid of started process 117 | } 118 | 119 | // NewNode create new node context with specified name and cookie string 120 | func NewNode(name string, cookie string) (node *Node) { 121 | nLog("Start with name '%s' and cookie '%s'", name, cookie) 122 | // TODO: add fqdn support 123 | ns := strings.Split(name, "@") 124 | nodeInfo := epmd.NodeInfo{ 125 | FullName: name, 126 | Name: ns[0], 127 | Domain: ns[1], 128 | Port: 0, 129 | Type: 77, // or 72 if hidden 130 | Protocol: 0, 131 | HighVsn: 5, 132 | LowVsn: 5, 133 | Creation: 0, 134 | } 135 | 136 | registry := ®istryChan{ 137 | storeChan: make(chan regReq), 138 | regNameChan: make(chan regNameReq), 139 | unregNameChan: make(chan unregNameReq), 140 | } 141 | 142 | node = &Node{ 143 | NodeInfo: nodeInfo, 144 | Cookie: cookie, 145 | registry: registry, 146 | channels: make(map[etf.Pid]procChannels), 147 | registered: make(map[etf.Atom]etf.Pid), 148 | neighbors: make(map[etf.Atom]nodeConn), 149 | } 150 | return node 151 | } 152 | 153 | func (n *Node) prepareProcesses() { 154 | n.sysProcs.netKernel = new(netKernel) 155 | n.Spawn(n.sysProcs.netKernel) 156 | 157 | n.sysProcs.globalNameServer = new(globalNameServer) 158 | n.Spawn(n.sysProcs.globalNameServer) 159 | 160 | n.sysProcs.rpcRex = new(rpcRex) 161 | n.Spawn(n.sysProcs.rpcRex) 162 | } 163 | 164 | // Spawn create new process and store its identificator in table at current node 165 | func (n *Node) Spawn(pd Process, args ...interface{}) (pid etf.Pid) { 166 | options := pd.Options() 167 | chanSize, ok := options["chan-size"].(int) 168 | if !ok { 169 | chanSize = 100 170 | } 171 | ctlChanSize, ok := options["chan-size"].(int) 172 | if !ok { 173 | chanSize = 100 174 | } 175 | in := make(chan etf.Term, chanSize) 176 | inFrom := make(chan etf.Tuple, chanSize) 177 | ctl := make(chan etf.Term, ctlChanSize) 178 | initCh := make(chan bool) 179 | pcs := procChannels{ 180 | in: in, 181 | inFrom: inFrom, 182 | ctl: ctl, 183 | init: initCh, 184 | } 185 | pid = n.storeProcess(pcs) 186 | pd.setNode(n) 187 | pd.setPid(pid) 188 | go pd.(Behaviour).ProcessLoop(pcs, pd, args...) 189 | <-initCh 190 | return 191 | } 192 | 193 | // Register associates the name with pid 194 | func (n *Node) Register(name etf.Atom, pid etf.Pid) { 195 | r := regNameReq{name: name, pid: pid} 196 | n.registry.regNameChan <- r 197 | } 198 | 199 | // Unregister removes the registered name 200 | func (n *Node) Unregister(name etf.Atom) { 201 | r := unregNameReq{name: name} 202 | n.registry.unregNameChan <- r 203 | } 204 | 205 | // Registered returns a list of names which have been registered using Register 206 | func (n *Node) Registered() (pids []etf.Atom) { 207 | pids = make([]etf.Atom, len(n.registered)) 208 | i := 0 209 | for p, _ := range n.registered { 210 | pids[i] = p 211 | i++ 212 | } 213 | return 214 | } 215 | 216 | func (n *Node) registrator() { 217 | for { 218 | select { 219 | case req := <-n.registry.storeChan: 220 | // FIXME: make proper allocation, now it just stub 221 | var id uint32 = 0 222 | for k, _ := range n.channels { 223 | if k.Id >= id { 224 | id = k.Id + 1 225 | } 226 | } 227 | var pid etf.Pid 228 | pid.Node = etf.Atom(n.FullName) 229 | pid.Id = id 230 | pid.Serial = 0 // FIXME 231 | pid.Creation = byte(n.Creation) 232 | 233 | n.channels[pid] = req.channels 234 | req.replyTo <- pid 235 | case req := <-n.registry.regNameChan: 236 | n.registered[req.name] = req.pid 237 | case req := <-n.registry.unregNameChan: 238 | delete(n.registered, req.name) 239 | } 240 | } 241 | } 242 | 243 | func (n *Node) storeProcess(chs procChannels) (pid etf.Pid) { 244 | myChan := make(chan etf.Pid) 245 | n.registry.storeChan <- regReq{replyTo: myChan, channels: chs} 246 | pid = <-myChan 247 | return pid 248 | } 249 | 250 | // Publish allow node be visible to other Erlang nodes via publishing port in EPMD 251 | func (n *Node) Publish(port int) (err error) { 252 | nLog("Publish ENode at %d", port) 253 | l, err := net.Listen("tcp", net.JoinHostPort("", strconv.Itoa(port))) 254 | if err != nil { 255 | return 256 | } 257 | n.Port = uint16(port) 258 | aliveResp := make(chan uint16) 259 | go epmdC(n, aliveResp) 260 | creation := <-aliveResp 261 | switch creation { 262 | case 99: 263 | return fmt.Errorf("Duplicate name '%s'", n.Name) 264 | case 100: 265 | return fmt.Errorf("Cannot connect to EPMD") 266 | default: 267 | n.Creation = creation 268 | } 269 | 270 | go func() { 271 | for { 272 | conn, err := l.Accept() 273 | nLog("Accept new at ENode") 274 | if err != nil { 275 | nLog(err.Error()) 276 | } else { 277 | wchan := make(chan []etf.Term, 10) 278 | ndchan := make(chan *dist.NodeDesc) 279 | go n.mLoopReader(conn, wchan, ndchan) 280 | go n.mLoopWriter(conn, wchan, ndchan) 281 | } 282 | } 283 | }() 284 | go n.registrator() 285 | n.prepareProcesses() 286 | return nil 287 | } 288 | 289 | func (currNode *Node) mLoopReader(c net.Conn, wchan chan []etf.Term, ndchan chan *dist.NodeDesc) { 290 | 291 | currNd := dist.NewNodeDesc(currNode.FullName, currNode.Cookie, false) 292 | ndchan <- currNd 293 | for { 294 | terms, err := currNd.ReadMessage(c) 295 | if err != nil { 296 | nLog("Enode error: %s", err.Error()) 297 | break 298 | } 299 | currNode.handleTerms(c, wchan, terms) 300 | } 301 | c.Close() 302 | } 303 | 304 | func (currNode *Node) mLoopWriter(c net.Conn, wchan chan []etf.Term, ndchan chan *dist.NodeDesc) { 305 | 306 | currNd := <-ndchan 307 | 308 | for { 309 | terms := <-wchan 310 | err := currNd.WriteMessage(c, terms) 311 | if err != nil { 312 | nLog("Enode error: %s", err.Error()) 313 | break 314 | } 315 | } 316 | c.Close() 317 | } 318 | 319 | func (currNode *Node) handleTerms(c net.Conn, wchan chan []etf.Term, terms []etf.Term) { 320 | nLog("Node terms: %#v", terms) 321 | 322 | if len(terms) == 0 { 323 | return 324 | } 325 | switch t := terms[0].(type) { 326 | case etf.Tuple: 327 | if len(t) > 0 { 328 | switch act := t.Element(1).(type) { 329 | case int: 330 | switch act { 331 | case REG_SEND: 332 | if len(terms) == 2 { 333 | currNode.RegSend(t.Element(2), t.Element(4), terms[1]) 334 | } else { 335 | nLog("*** ERROR: bad REG_SEND: %#v", terms) 336 | } 337 | default: 338 | nLog("Unhandled node message (act %d): %#v", act, t) 339 | } 340 | case etf.Atom: 341 | switch act { 342 | case etf.Atom("$go_set_node"): 343 | nLog("SET NODE %#v", t) 344 | currNode.neighbors[t[1].(etf.Atom)] = nodeConn{conn: c, wchan: wchan} 345 | } 346 | default: 347 | nLog("UNHANDLED ACT: %#v", t.Element(1)) 348 | } 349 | } 350 | } 351 | } 352 | 353 | // RegSend sends message from one process to registered 354 | func (currNode *Node) RegSend(from, to etf.Term, message etf.Term) { 355 | nLog("REG_SEND: From: %#v, To: %#v, Message: %#v", from, to, message) 356 | var toPid etf.Pid 357 | switch tp := to.(type) { 358 | case etf.Pid: 359 | toPid = tp 360 | case etf.Atom: 361 | toPid = currNode.Whereis(tp) 362 | } 363 | currNode.SendFrom(from, toPid, message) 364 | } 365 | 366 | // Whereis returns pid of registered process 367 | func (currNode *Node) Whereis(who etf.Atom) (pid etf.Pid) { 368 | pid, _ = currNode.registered[who] 369 | return 370 | } 371 | 372 | // SendFrom sends message from source to destination 373 | func (currNode *Node) SendFrom(from etf.Term, to etf.Pid, message etf.Term) { 374 | nLog("SendFrom: %#v, %#v, %#v", from, to, message) 375 | pcs := currNode.channels[to] 376 | pcs.inFrom <- etf.Tuple{from, message} 377 | } 378 | 379 | // Send sends message to destination process withoud source 380 | func (currNode *Node) Send(to etf.Pid, message etf.Term) { 381 | nLog("Send: %#v, %#v", to, message) 382 | if string(to.Node) == currNode.FullName { 383 | nLog("Send to local node") 384 | pcs := currNode.channels[to] 385 | pcs.in <- message 386 | } else { 387 | nLog("Send to remote node: %#v, %#v", to, currNode.neighbors[to.Node]) 388 | 389 | msg := []etf.Term{etf.Tuple{SEND, etf.Atom(""), to}, message} 390 | currNode.neighbors[to.Node].wchan <- msg 391 | } 392 | } 393 | 394 | func epmdC(n *Node, resp chan uint16) { 395 | conn, err := net.Dial("tcp", "127.0.0.1:4369") 396 | if err != nil { 397 | nLog("Error calling net.Dial : %s", err.Error()) 398 | resp <- 100 399 | return 400 | } 401 | defer conn.Close() 402 | 403 | epmdFROM := make(chan []byte) 404 | go epmdREADER(conn, epmdFROM) 405 | 406 | epmdTO := make(chan []byte) 407 | go epmdWRITER(conn, epmdFROM, epmdTO) 408 | 409 | epmdTO <- epmd.Compose_ALIVE2_REQ(&n.NodeInfo) 410 | 411 | for { 412 | 413 | select { 414 | case reply := <-epmdFROM: 415 | nLog("From EPMD: %v", reply) 416 | 417 | switch epmd.MessageId(reply[0]) { 418 | case epmd.ALIVE2_RESP: 419 | if creation, ok := epmd.Read_ALIVE2_RESP(reply); ok { 420 | resp <- creation 421 | } else { 422 | resp <- 99 423 | } 424 | } 425 | 426 | } 427 | 428 | } 429 | } 430 | 431 | func epmdREADER(conn net.Conn, in chan []byte) { 432 | for { 433 | buf := make([]byte, 1024) 434 | n, err := conn.Read(buf) 435 | if err != nil { 436 | in <- buf[0:n] 437 | in <- []byte{} 438 | return 439 | } 440 | nLog("Read from EPMD %d: %v", n, buf[:n]) 441 | in <- buf[:n] 442 | } 443 | } 444 | 445 | func epmdWRITER(conn net.Conn, in chan []byte, out chan []byte) { 446 | for { 447 | select { 448 | case data := <-out: 449 | buf := make([]byte, 2) 450 | binary.BigEndian.PutUint16(buf[0:2], uint16(len(data))) 451 | buf = append(buf, data...) 452 | _, err := conn.Write(buf) 453 | if err != nil { 454 | in <- []byte{} 455 | } 456 | } 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /rpc.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/goerlang/etf" 5 | ) 6 | 7 | type rpcFunction func(etf.List) etf.Term 8 | 9 | type modFun struct { 10 | module string 11 | function string 12 | } 13 | 14 | type rpcRex struct { 15 | GenServerImpl 16 | callMap map[modFun]rpcFunction 17 | } 18 | 19 | func (currNode *Node) RpcProvide(modName string, funName string, fun rpcFunction) (err error) { 20 | nLog("Provide: %s:%s %#v", modName, funName, fun) 21 | currNode.sysProcs.rpcRex.callMap[modFun{modName, funName}] = fun 22 | return 23 | } 24 | 25 | func (currNode *Node) RpcRevoke(modName, funName string) { 26 | nLog("Revoke: %s:%s", modName, funName) 27 | } 28 | 29 | func (rpcs *rpcRex) Init(args ...interface{}) { 30 | nLog("REX: Init: %#v", args) 31 | rpcs.Node.Register(etf.Atom("rex"), rpcs.Self) 32 | rpcs.callMap = make(map[modFun]rpcFunction, 0) 33 | } 34 | 35 | func (rpcs *rpcRex) HandleCast(message *etf.Term) { 36 | nLog("REX: HandleCast: %#v", *message) 37 | } 38 | 39 | func (rpcs *rpcRex) HandleCall(message *etf.Term, from *etf.Tuple) (reply *etf.Term) { 40 | nLog("REX: HandleCall: %#v, From: %#v", *message, *from) 41 | var replyTerm etf.Term 42 | valid := false 43 | switch req := (*message).(type) { 44 | case etf.Tuple: 45 | if len(req) > 0 { 46 | switch act := req[0].(type) { 47 | case etf.Atom: 48 | if string(act) == "call" { 49 | valid = true 50 | if fun, ok := rpcs.callMap[modFun{string(req[1].(etf.Atom)), string(req[2].(etf.Atom))}]; ok { 51 | replyTerm = fun(req[3].(etf.List)) 52 | } else { 53 | replyTerm = etf.Term(etf.Tuple{etf.Atom("badrpc"), etf.Tuple{etf.Atom("EXIT"), etf.Tuple{etf.Atom("undef"), etf.List{etf.Tuple{req[1], req[2], req[3], etf.List{}}}}}}) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | if !valid { 60 | replyTerm = etf.Term(etf.Tuple{etf.Atom("badrpc"), etf.Atom("unknown")}) 61 | } 62 | reply = &replyTerm 63 | return 64 | } 65 | 66 | func (rpcs *rpcRex) HandleInfo(message *etf.Term) { 67 | nLog("REX: HandleInfo: %#v", *message) 68 | } 69 | 70 | func (rpcs *rpcRex) Terminate(reason interface{}) { 71 | nLog("REX: Terminate: %#v", reason.(int)) 72 | } 73 | --------------------------------------------------------------------------------