diff options
author | tomsmeding <tom.smeding@gmail.com> | 2019-02-16 12:59:19 +0100 |
---|---|---|
committer | tomsmeding <tom.smeding@gmail.com> | 2019-02-16 18:20:20 +0100 |
commit | 1154c8605a5c42a651bac5e2f74839062d352b9e (patch) | |
tree | 42527395c2eec9566d0e23b8bd17e5c59239b1dd | |
parent | e2279628e551e5c82f70c739f6c02671e6ccd9fd (diff) |
Add referee features
-rw-r--r-- | main.cpp | 25 | ||||
-rw-r--r-- | process.cpp | 77 | ||||
-rw-r--r-- | process.h | 1 | ||||
-rw-r--r-- | referee.cpp | 127 | ||||
-rw-r--r-- | referee.h | 43 |
5 files changed, 216 insertions, 57 deletions
@@ -257,14 +257,26 @@ static void playMatch(Player &p1, Player &p2, int index, const Params ¶ms) { procs[0].run(); procs[1].run(); + const auto handleWriteLines = [&referee, &procs]() { + for (const auto &p : referee.playerWriteLines()) { + assert(0 <= p.first && p.first < 2); + procs[p.first].writeLine(p.second); + } + }; + + // Received upon initialisation of referee + handleWriteLines(); + string lastMove = "Start"; while (true) { for (int i = 0; i < 2; i++) { - if (!procs[i].writeLine(lastMove)) { - cout << "ERROR writing move to player " << i+1 - << " (game " << gameCodeName(p1, p2, index) << ")" << endl; - throw StopCompetitionError(); + if (!referee.getFeatures().noLastMove) { + if (!procs[i].writeLine(lastMove)) { + cout << "ERROR writing move to player " << i+1 + << " (game " << gameCodeName(p1, p2, index) << ")" << endl; + throw StopCompetitionError(); + } } procs[i].unStop(); @@ -296,10 +308,7 @@ static void playMatch(Player &p1, Player &p2, int index, const Params ¶ms) { throw StopCompetitionError(); } - for (const auto &p : referee.playerWriteLines()) { - assert(0 <= p.first && p.first < 2); - procs[p.first].writeLine(p.second); - } + handleWriteLines(); if (referee.gameEnded()) { mres.status = MatchResult::Status::ok; diff --git a/process.cpp b/process.cpp index 41325c3..061737e 100644 --- a/process.cpp +++ b/process.cpp @@ -3,6 +3,8 @@ #include <fcntl.h> #include <signal.h> #include <sys/wait.h> +#include <sys/select.h> +#include <sys/time.h> #include "process.h" #include "error.h" @@ -189,6 +191,81 @@ optional<string> Process::readLine() { } } +optional<string> Process::readLineTimeout(size_t millis) { + if (pid == -1) { + cout << "-- readLineTimeout on pid==-1 --" << endl; + return nullopt; + } + + size_t idx = readBuf.find('\n'); + if (idx != string::npos) { + string res = readBuf.substr(0, idx); + readBuf = readBuf.substr(idx + 1); + return res; + } + + struct timeval start; + gettimeofday(&start, nullptr); + + struct timeval totaltime; + totaltime.tv_sec = millis / 1000; + totaltime.tv_usec = millis % 1000 * 1000; + + struct timeval left = totaltime; + + while (true) { + string s(1024, '\0'); + + ssize_t nr; + + fd_set inset; + FD_ZERO(&inset); + FD_SET(outfd, &inset); + int ret = select(outfd + 1, &inset, nullptr, nullptr, &left); + if (ret < 0) { + if (errno == EINTR) goto continue_adjust_time_left; + perror("select"); + return nullopt; + } + + if (!FD_ISSET(outfd, &inset)) goto continue_adjust_time_left; + + nr = read(outfd, &s[0], s.size()); + if (nr < 0) { + if (errno == EINTR) goto continue_adjust_time_left; + perror("read"); + return nullopt; + } + if (nr == 0) { // EOF + cout << "-- eof in readLine --" << endl; + return nullopt; + } + s.resize(nr); + + idx = s.find('\n'); + if (idx != string::npos) { + string res = readBuf + s.substr(0, idx); + readBuf = s.substr(idx + 1); + return res; + } + + readBuf += s; + + continue_adjust_time_left: + struct timeval now, delta; + gettimeofday(&now, nullptr); + timersub(&now, &start, &delta); + timersub(&totaltime, &delta, &left); + + struct timeval zero; + timerclear(&zero); + // Not greater, so less-equal; c.f. manpage + if (!timercmp(&left, &zero, >)) { + return nullopt; // Exceeded timeout + } + } +} + int Process::terminate() { // TODO: send only SIGTERM, then wait a little while, then send // SIGKILL if necessary @@ -40,4 +40,5 @@ public: bool writeLine(const string_view line); optional<string> readLine(); + optional<string> readLineTimeout(size_t millis); }; diff --git a/referee.cpp b/referee.cpp index 15f49c3..ffcbd14 100644 --- a/referee.cpp +++ b/referee.cpp @@ -3,9 +3,15 @@ #include "referee.h" #include "params.h" +// Timeout in milliseconds for the feature negotiation at the start of the +// protocol. If the referee hasn't sent anything after this timeout, we assume +// the referee isn't capable of sending feature lines and will be treated as +// such for the duration of the competition. +#define FEATURE_TIMEOUT 200 + Referee::Referee(const string_view execname, const vector<string> &players) - : proc(execname) { + : proc(execname), refereeExecname(execname) { proc.run(); @@ -14,11 +20,15 @@ Referee::Referee(const string_view execname, const vector<string> &players) for (const string &p : players) cout << " " << p; cout << endl; } - + + queryFeatures(); + proc.writeLine(to_string(players.size())); for (const string &p : players) { proc.writeLine(p); } + + readWriteLines(); } Referee::~Referee() { @@ -39,49 +49,32 @@ bool Referee::moveValid(int player, const string_view line) { proc.writeLine(s); - optional<string> response = readLine(); + optional<string> response = proc.readLine(); if (response) { + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") read <" << *response << ">" << endl; + } + bool result; if (*response == "valid end") { isEnd = true; - readScores(); result = true; } else if (*response == "valid") { result = true; - } else { + } else if (*response == "invalid") { result = false; + } else { + cout << "REF(" << proc.getPid() << ") Invalid validity judgement '" << *response << "'!" << endl; + exit(1); } - if (featureWriteLines) { - writeLinesList.clear(); - - while (true) { - optional<string> line = readLine(); - if (!line) { - if (referee_verbose) { - cout << "REF(" << proc.getPid() << ") EOF!" << endl; - } - return false; - } - - if (line == "write_end") break; + readWriteLines(); - size_t idx = line->find(' '); - if (idx == string::npos) { - cerr << "Referee feature_write_lines protocol violation!" << endl; - exit(1); - } - - int player = stoi(line->substr(0, idx)); - writeLinesList.emplace_back(player, line->substr(idx + 1)); - } - } + if (isEnd) readScores(); return result; } else { - if (referee_verbose) { - cout << "REF(" << proc.getPid() << ") EOF!" << endl; - } + cout << "REF(" << proc.getPid() << ") EOF!" << endl; return false; } } @@ -94,28 +87,80 @@ bool Referee::gameEnded() { return isEnd; } -optional<string> Referee::readLine() { +void Referee::queryFeatures() { + bool first = true; + + while (true) { + optional<string> line; + if (first) { + line = proc.readLineTimeout(FEATURE_TIMEOUT); + if (!line) { + cout << "Referee '" << refereeExecname << "' seems to follow the old protocol!" << endl; + return; + } + + first = false; + } else { + line = proc.readLine(); + } + + if (!line) { + cout << "REF(" << proc.getPid() << ") EOF!" << endl; + exit(1); + } + + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl; + } + + if (*line == "feature_end") { + return; + } else if (line->substr(0, 8) == "feature ") { + string featname = line->substr(8); + if (featname == "write_lines") features.writeLines = true; + else if (featname == "no_last_move") features.noLastMove = true; + else { + cerr << "REF(" << proc.getPid() << ") Referee requested unsupported feature '" << featname << "'" << endl; + exit(1); + } + } else { + cerr << "REF(" << proc.getPid() << ") Referee protocol violation in feature negotiation" << endl; + exit(1); + } + } +} + +void Referee::readWriteLines() { + writeLinesList.clear(); + + if (!features.writeLines) return; + while (true) { optional<string> line = proc.readLine(); - if (!line) return line; + if (!line) { + cout << "REF(" << proc.getPid() << ") EOF!" << endl; + break; + } if (referee_verbose) { cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl; } - if (line->substr(0, 8) != "feature ") return line; + if (line == "write_end") break; - string featname = line->substr(8); - if (featname == "write_lines") featureWriteLines = true; - else { - cerr << "Referee requested unsupported feature '" << featname << "'" << endl; + size_t idx = line->find(' '); + if (idx == string::npos) { + cerr << "REF(" << proc.getPid() << ") Referee feature_write_lines protocol violation!" << endl; exit(1); } + + int player = stoi(line->substr(0, idx)); + writeLinesList.emplace_back(player, line->substr(idx + 1)); } } void Referee::readScores() { - optional<string> line = readLine(); + optional<string> line = proc.readLine(); if (!line) { cerr << "Referee stopped before providing scores" << endl; exit(1); @@ -138,3 +183,7 @@ optional<vector<int>> Referee::getScores() { void Referee::terminate() { proc.terminate(); } + +const Features& Referee::getFeatures() const { + return features; +} @@ -10,16 +10,28 @@ using namespace std; +struct Features { + // If true, players should not receive the last move on stdin. + // Used by the match runner. + bool noLastMove = false; + + // If true, the referee program can write additional lines to the players. + // Used by the Referee class to produce playerWriteLines() output. + bool writeLines = false; +}; + class Referee { Process proc; + string refereeExecname; bool isEnd = false; vector<int> scores; - bool featureWriteLines = false; + Features features; vector<pair<int, string>> writeLinesList; - optional<string> readLine(); + void queryFeatures(); void readScores(); + void readWriteLines(); public: Referee(const string_view execname, const vector<string> &players); @@ -34,13 +46,20 @@ public: // Kills the referee process void terminate(); + + const Features& getFeatures() const; }; /* Referee protocol: -At startup, the referee receives a line containing the number of players in the -game, followed by that number of lines containing the players' names. +At startup, the referee should write a number of lines to stdout of the form +'feature <name>', indicating that the referee supports the feature given by +<name>. The list of features should be ended with a line containing +'feature_end'. For supported features and their effect, see below. + +The referee then receives a line containing the number of players in the game, +followed by that number of lines containing the players' names. Then, it repeatedly receives a line on stdin of the form '<player> <line>', where <player> is the index of the player in the game (0 is first, 1 is second, @@ -53,12 +72,16 @@ then write a line to stdout that is equal to either 'valid', 'invalid', or the game. FEATURES -At any time the referee may write 'feature <name>' to stdout, which enables the -protocol feature with the given name. The following features are supported: -- 'write_lines': After a move validity judgement, the referee should write lines +The following features are supported: +- 'write_lines': After having received the list of players at the start of the + protocol, and after a move validity judgement, the referee should write lines of the form '<player> <line>' to stdout, where <player> is the index of the player on whose stdin to write <line>. The referee should signal the end of - this list by writing a line 'write_end' to stdout. - This allows the referee to write additional data to players' inputs after they - have written their moves. + this list by writing a line 'write_end' to stdout. This allows the referee to + write additional data to players' inputs after they have written their moves. + Note that 'valid end' is also a move validity judgement, and the player lines + should be written *before* writing the scores. +- 'no_last_move': Prevent the competition manager from writing the last player's + last move to stdin of the next player. Should probably be used in conjunction + with 'write_lines'. */ |