// The core ZeroMQ messaging loop. Handles requests and responses // asynchronously using the router socket. Every request is delegated to // a goroutine for maximum concurrency. // // `gozmq` does currently not support copy-free messages/frames. This // means that every message passing through this function needs to be // copied in-memory. If this becomes a bottleneck in the future, // multiple router sockets can be hooked to this final router to scale // message copying. // // TODO: Make this a type function of `Server` to remove a lot of // parameters. func loopServer(estore *eventstore.EventStore, evpubsock, frontend zmq.Socket, stop chan bool) { toPoll := zmq.PollItems{ zmq.PollItem{Socket: &frontend, zmq.Events: zmq.POLLIN}, } pubchan := make(chan eventstore.StoredEvent) estore.RegisterPublishedEventsChannel(pubchan) go publishAllSavedEvents(pubchan, evpubsock) defer close(pubchan) pollchan := make(chan zmqPollResult) respchan := make(chan zMsg) pollCancel := make(chan bool) defer stopPoller(pollCancel) go asyncPoll(pollchan, toPoll, pollCancel) for { select { case res := <-pollchan: if res.err != nil { log.Println("Could not poll:", res.err) } if res.err == nil && toPoll[0].REvents&zmq.POLLIN != 0 { msg, _ := toPoll[0].Socket.RecvMultipart(0) zmsg := zMsg(msg) go handleRequest(respchan, estore, zmsg) } go asyncPoll(pollchan, toPoll, pollCancel) case frames := <-respchan: if err := frontend.SendMultipart(frames, 0); err != nil { log.Println(err) } case <-stop: log.Println("Server asked to stop. Stopping...") return } } }
// Handles a single ZeroMQ RES/REQ loop synchronously. // // The full request message stored in `msg` and the full ZeroMQ response // is pushed to `respchan`. The function does not return any error // because it is expected to be called asynchronously as a goroutine. func handleRequest(respchan chan zMsg, estore *eventstore.EventStore, msg zMsg) { // TODO: Rename to 'framelist' parts := list.New() for _, msgpart := range msg { parts.PushBack(msgpart) } resptemplate := list.New() emptyFrame := zFrame("") for true { resptemplate.PushBack(parts.Remove(parts.Front())) if bytes.Equal(parts.Front().Value.(zFrame), emptyFrame) { break } } if parts.Len() == 0 { errstr := "Incoming command was empty. Ignoring it." log.Println(errstr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + errstr)) respchan <- listToFrames(response) return } command := string(parts.Front().Value.(zFrame)) switch command { case "PUBLISH": parts.Remove(parts.Front()) if parts.Len() != 2 { // TODO: Constantify this error message errstr := "Wrong number of frames for PUBLISH." log.Println(errstr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + errstr)) respchan <- listToFrames(response) } else { estream := parts.Remove(parts.Front()) data := parts.Remove(parts.Front()) newevent := eventstore.Event{ estream.(eventstore.StreamName), data.(zFrame), } newId, err := estore.Add(newevent) if err != nil { sErr := err.Error() log.Println(sErr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + sErr)) respchan <- listToFrames(response) } else { // the event was added response := copyList(resptemplate) response.PushBack(zFrame("PUBLISHED")) response.PushBack(zFrame(newId)) respchan <- listToFrames(response) } } case "QUERY": parts.Remove(parts.Front()) if parts.Len() != 3 { // TODO: Constantify this error message errstr := "Wrong number of frames for QUERY." log.Println(errstr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + errstr)) respchan <- listToFrames(response) } else { estream := parts.Remove(parts.Front()) fromid := parts.Remove(parts.Front()) toid := parts.Remove(parts.Front()) req := eventstore.QueryRequest{ Stream: estream.(zFrame), FromId: fromid.(zFrame), ToId: toid.(zFrame), } events, err := estore.Query(req) if err != nil { sErr := err.Error() log.Println(sErr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + sErr)) respchan <- listToFrames(response) } else { for eventdata := range events { response := copyList(resptemplate) response.PushBack([]byte("EVENT")) response.PushBack(eventdata.Id) response.PushBack(eventdata.Data) respchan <- listToFrames(response) } response := copyList(resptemplate) response.PushBack(zFrame("END")) respchan <- listToFrames(response) } } default: // TODO: Move these error strings out as constants of // this package. // TODO: Move the chunk of code below into a separate // function and reuse for similar piece of code above. // TODO: Constantify this error message errstr := "Unknown request type." log.Println(errstr) response := copyList(resptemplate) response.PushBack(zFrame("ERROR " + errstr)) respchan <- listToFrames(response) } }