func ProcessingLoop() { var conn net.Conn = nil var nextRetryResp *TaskResponse = nil var taskCompletionChan <-chan *TaskResponse = nil var connectDelay time.Duration var doScoreReload bool = false // kick off a new connection attempt. go connectMe(connectDelay) // and this is where we spin! for { var retryDelay time.Duration = 0 var retryChan <-chan time.Time = nil if conn != nil { for nextRetryResp == nil { nextRetryResp = getNextUnacknowledgedResponse() if nil == nextRetryResp { break } retryDelay = nextRetryResp.RetryTime.Sub(time.Now()) if retryDelay < 0 { sendResponse(conn, nextRetryResp) nextRetryResp = nil } } if nextRetryResp != nil { retryChan = time.After(retryDelay) } } if taskCompletionChan == nil { nextTask := getNextPendingTask() if nextTask != nil { taskCompletionChan = ExecuteTask(nextTask) } else { if conn != nil && !pendingTaskRequest { o.Debug("Asking for trouble") p := o.MakeReadyForTask() p.Send(conn) o.Debug("Sent Request for trouble") pendingTaskRequest = true } } } select { // Currently executing job finishes. case newresp := <-taskCompletionChan: o.Debug("job%d: Completed with State %s\n", newresp.id, newresp.State) // preemptively set a retrytime. newresp.RetryTime = time.Now() // ENOCONN - sub it in as our next retryresponse, and prepend the old one onto the queue. if nil == conn { if nil != nextRetryResp { prequeueResponse(nextRetryResp) } o.Debug("job%d: Queuing Initial Response", newresp.id) nextRetryResp = newresp } else { o.Debug("job%d: Sending Initial Response", newresp.id) sendResponse(conn, newresp) } if doScoreReload { o.Info("Performing Deferred score reload") LoadScores() doScoreReload = false } taskCompletionChan = nil // If the current unacknowledged response needs a retry, send it. case <-retryChan: sendResponse(conn, nextRetryResp) nextRetryResp = nil // New connection. Set up the receiver thread and Introduce ourselves. case nci := <-newConnection: if conn != nil { conn.Close() } conn = nci.conn connectDelay = nci.timeout pendingTaskRequest = false // start the reader go Reader(conn) /* Introduce ourself */ p := o.MakeIdentifyClient(LocalHostname, PlayerVersion) p.Send(conn) // Lost connection. Shut downt he connection. case <-lostConnection: o.Warn("Lost Connection to Master") conn.Close() conn = nil // restart the connection attempts go connectMe(connectDelay) // Message received from master. Decode and action. case p := <-receivedMessage: // because the message could possibly be an ACK, push the next retry response back into the queue so acknowledge can find it. if nil != nextRetryResp { prequeueResponse(nextRetryResp) nextRetryResp = nil } var upkt interface{} = nil if p.Length > 0 { var err error upkt, err = p.Decode() o.MightFail(err, "Couldn't decode packet from master") } handler, exists := dispatcher[p.Type] if exists { connectDelay = 0 handler(conn, upkt) } else { o.Fail("Unhandled Pkt Type %d", p.Type) } // Reload scores case <-reloadScores: // fortunately this is actually completely safe as // long as nobody's currently executing. // who'd have thunk it? if taskCompletionChan == nil { o.Info("Reloading scores") LoadScores() } else { o.Info("Deferring score reload (execution in progress)") doScoreReload = true } // Keepalive delay expired. Send Nop. case <-time.After(KeepaliveDelay): if conn == nil { break } o.Debug("Sending NOP") p := o.MakeNop() p.Send(conn) } } }
/* C->P only messages, should never appear on the wire. */ o.TypeTaskRequest: handleIllegal, } var loopFudge time.Duration = 10 * time.Millisecond /* 10 ms should be enough fudgefactor */ func clientLogic(client *ClientInfo) { loop := true for loop { var retryWait <-chan time.Time var retryTask *TaskRequest if client.Player != "" { var waitTime, now time.Time cleanPass := false attempts := 0 for !cleanPass && attempts < 10 { /* reset our state for the pass */ waitTime = time.Time{} retryTask = nil attempts++ cleanPass = true now = time.Now().Add(loopFudge) // if the client is correctly associated, // evaluate all jobs for outstanding retries, // and work out when our next retry is due. for _, v := range client.pendingTasks { if v.RetryTime.Before(now) { client.SendTask(v) cleanPass = false } else { if waitTime == (time.Time{}) || v.RetryTime.Before(waitTime) { retryTask = v waitTime = v.RetryTime } } } } if attempts > 10 { o.Fail("Couldn't find next timeout without restarting excessively") } if retryTask != nil { retryWait = time.After(waitTime.Sub(time.Now())) } } select { case <-retryWait: client.SendTask(retryTask) case p := <-client.PktInQ: /* we've received a packet. do something with it. */ if client.Player == "" && p.Type != o.TypeIdentifyClient { o.Warn("Client %s: didn't identify self,- got type %d instead", client.Name(), p.Type) client.Abort() break } var upkt interface{} if p.Length > 0 { var err error upkt, err = p.Decode() if err != nil { o.Warn("Client %s: error unmarshalling message: %s", client.Name(), err) client.Abort() break } } handler, exists := dispatcher[p.Type] if exists { handler(client, upkt) } else { o.Warn("Client %s: Unhandled packet type: %d", client.Name(), p.Type) } case p := <-client.PktOutQ: if p != nil { client.sendNow(p) } case t := <-client.TaskQ: client.GotTask(t) case <-client.abortQ: loop = false case <-time.After(KeepaliveDelay): p := o.MakeNop() _, err := p.Send(client.connection) if err != nil { o.Warn("Client %s: error sending packet: %s", client.Name(), err) client.Abort() } } } client.connection.Close() }