...
 
Commits (3)
......@@ -22,23 +22,19 @@ public class Bot : OVCCClient.Client
debug ("Trying to login...");
while (true) {
try {
yield login ("""Clever bot %03u""".printf (player_suffix), "xxx");
yield login ("""Clever bot %03u""".printf (player_suffix), "xxx",
new OVCCClient.TimeoutCancellable().start());
break;
} catch (OVCCClient.ServerError.NICKNAME_IN_USE e1) {
player_suffix++;
} catch (OVCCClient.ServerError.MISSING_AUTHENTICATION e2) {
leave ();
throw e2; /* avoid infinite loop */
} catch (Error e3) {
leave ();
throw e3;
}
}
debug ("Logged in with player_suffix = %u", player_suffix);
/* join an open game */
var list = yield server.enumerate_games (OVCC.GameState.NEW | OVCC.GameState.PLAYER_WAITING);
var list = yield server.enumerate_games (OVCC.GameState.NEW | OVCC.GameState.PLAYER_WAITING,
new OVCCClient.TimeoutCancellable().start());
print ("List of open games:\n");
var idx = 0;
foreach (var d in list) {
......@@ -57,12 +53,8 @@ public class Bot : OVCCClient.Client
debug ("Trying to join game %i...", game_to_join);
try {
yield join_game (game_to_join);
} catch (Error e4) {
leave ();
throw e4;
}
yield join_game (game_to_join, new OVCCClient.TimeoutCancellable().start());
debug ("Joined");
/* react on game state changes */
......@@ -250,7 +242,7 @@ public int main (string[] args)
});
} catch (Error e) {
warning ("Failed to join on the server: %s", e.message);
loop.quit ();
try { bot.leave (); } catch { /* we don't care if disconnection failed */ };
}
});
loop.run ();
......
......@@ -38,6 +38,45 @@ namespace OVCCClient
FAILED
}
public const uint NETWORK_TIMEOUT = 5;
/**
* A class providing a unified timeout-cancellable for queries to the server.
*/
[Compact]
public class TimeoutCancellable : Cancellable
{
public uint timeout { get; construct; }
public bool timed_out { get; private set; default = false; }
private uint source_id = 0;
public TimeoutCancellable (uint timeout = NETWORK_TIMEOUT)
{
Object (timeout: timeout);
}
public TimeoutCancellable start ()
{
source_id = Timeout.add_seconds (timeout, () => {
timed_out = true;
source_id = 0;
cancel();
return true;
});
return this;
}
public TimeoutCancellable stop ()
{
if (source_id != 0)
{
Source.remove (source_id);
source_id = 0;
}
return this;
}
}
/**
* A class representing a client that can connect to a server.
* Frontends should connect to the signals to know when player should play.
......@@ -110,12 +149,14 @@ namespace OVCCClient
public bool leave ()
throws ServerError
{
debug ("Leaving...");
sigqueue.remove_all ();
if (game != null) {
game.abort ();
}
if (server.is_connected ()) {
if (server != null && server.is_connected ()) {
server.disconnect_from ();
}
/* destroy server */
......
......@@ -92,6 +92,7 @@ namespace OVCCClient
private DataOutputStream output;
/* why the hell can't I use Thread<void> here but in new()?? */
private unowned Thread<bool> listen_loop_thread = null;
private int listen_loop_running = 0;
private Cancellable? listen_loop_cancel = null;
private unowned Thread<bool> send_loop_thread = null;
private int send_loop_running = 0;
......@@ -137,17 +138,19 @@ namespace OVCCClient
* the object anymore. This allows the field to be changed (e.g. to null)
* without problem for us. */
var cancellable = listen_loop_cancel;
var running = true;
while (running && ! cancellable.is_cancelled ()) {
while (AtomicInt.get (ref listen_loop_running) > 0 &&
! cancellable.is_cancelled ()) {
Message? msg;
try {
msg = Message.receive (input, cancellable);
} catch (IOError.CANCELLED e) {
debug ("Listen loop cancelled");
break;
} catch (Error e) {
warning ("error receiving data: %s", e.message);
critical ("Assuming broken channel, shutting down listen loop");
break;
}
......@@ -163,9 +166,12 @@ namespace OVCCClient
return false;
});
if (msg.message_type == MessageType.DISCONNECT) {
running = false; break;
break;
}
}
listen_loop_running = 0;
return true;
}
......@@ -193,6 +199,7 @@ namespace OVCCClient
try {
msg.send (output, cancellable);
} catch (IOError.CANCELLED e) {
debug ("Send loop cancelled");
break;
} catch (Error e) {
warning ("Trying to send message %s: %s", msg.get_type().name(), e.message);
......@@ -206,6 +213,9 @@ namespace OVCCClient
return false;
});
}
send_loop_running = 0;
return true;
}
......@@ -219,12 +229,16 @@ namespace OVCCClient
*/
public async Message receive_type (MessageType mtype,
Cancellable? cancellable = null)
throws IOError
throws ServerError, IOError
{
Message msg = null;
ulong id = 0;
ulong cancel_id = 0;
if (AtomicInt.get (ref listen_loop_running) == 0) {
throw new ServerError.COMMUNICATION_FAILED ("Listen loop is not running");
}
if (cancellable != null) {
cancel_id = cancellable.connect (() => {
cancel_id = 0;
......@@ -244,7 +258,15 @@ namespace OVCCClient
yield;
if (cancellable != null) {
cancellable.set_error_if_cancelled ();
if (cancellable is TimeoutCancellable) {
var c = (TimeoutCancellable) cancellable;
if (c.timed_out) {
/* switch to a more precise error */
throw new GLib.IOError.TIMED_OUT ("Cancelled by a timeout of %us", c.timeout);
}
} else {
cancellable.set_error_if_cancelled ();
}
}
return msg;
}
......@@ -292,7 +314,19 @@ namespace OVCCClient
if (err != null) {
throw err;
} else if (cancellable != null) {
cancellable.set_error_if_cancelled ();
if (cancellable.is_cancelled()) {
/* maybe not sufficient if already popped out */
send_loop_queue.remove(msg);
}
if (cancellable is TimeoutCancellable) {
var c = (TimeoutCancellable) cancellable;
if (c.timed_out) {
/* switch to a more precise error */
throw new GLib.IOError.TIMED_OUT ("Cancelled by a timeout of %us", c.timeout);
}
} else {
cancellable.set_error_if_cancelled ();
}
}
return true;
......@@ -318,6 +352,7 @@ namespace OVCCClient
output = new DataOutputStream (connection.output_stream);
listen_loop_cancel = new Cancellable ();
send_loop_cancel = new Cancellable ();
listen_loop_running = 1;
listen_loop_thread = new Thread<bool>.try ("listen loop", listen_loop);
send_loop_running = 1;
send_loop_thread = new Thread<bool>.try ("send loop", send_loop);
......@@ -346,12 +381,12 @@ namespace OVCCClient
throws ServerError
requires (connection != null)
{
if (listen_loop_cancel != null) {
if (listen_loop_cancel != null && AtomicInt.get (ref listen_loop_running) > 0) {
listen_loop_cancel.cancel ();
listen_loop_cancel = null;
listen_loop_thread.join ();
}
if (send_loop_cancel != null) {
if (send_loop_cancel != null && AtomicInt.get (ref send_loop_running) > 0) {
send_loop_cancel = null;
AtomicInt.set (ref send_loop_running, 0);
send_loop_thread.join ();
......