use a single thread and a protected queue

I don't want race conditions between threads talking to the server.
This commit is contained in:
Eric House 2017-10-23 21:07:05 -07:00
parent 6787fe1406
commit c3887b9c77

View file

@ -36,6 +36,9 @@ typedef struct _RelayConStorage {
pthread_t mainThread; pthread_t mainThread;
guint moveCheckerID; guint moveCheckerID;
XP_U16 nextMoveCheckSecs; XP_U16 nextMoveCheckSecs;
pthread_cond_t relayCondVar;
pthread_mutex_t relayMutex;
GSList* relayTaskList;
int socket; int socket;
RelayConnProcs procs; RelayConnProcs procs;
@ -75,12 +78,36 @@ static size_t writeVLI( XP_U8* out, uint32_t nn );
static size_t un2vli( int nn, uint8_t* buf ); static size_t un2vli( int nn, uint8_t* buf );
static bool vli2un( const uint8_t** inp, uint32_t* outp ); static bool vli2un( const uint8_t** inp, uint32_t* outp );
static void* relayThread( void* arg );
typedef struct _WriteState { typedef struct _WriteState {
gchar* ptr; gchar* ptr;
size_t curSize; size_t curSize;
} WriteState; } WriteState;
typedef enum { POST, QUERY, } TaskType;
typedef struct _RelayTask {
TaskType typ;
RelayConStorage* storage;
WriteState ws;
union {
struct {
XP_U8* msgbuf;
XP_U16 len;
} post;
struct {
GHashTable* map;
} query;
} u;
} RelayTask;
static RelayTask* makeRelayTask(RelayConStorage* storage, TaskType typ);
static void freeRelayTask(RelayTask* task);
static void handlePost( RelayTask* task );
static void handleQuery( RelayTask* task );
static size_t static size_t
write_callback(void *contents, size_t size, size_t nmemb, void* data) write_callback(void *contents, size_t size, size_t nmemb, void* data)
{ {
@ -138,8 +165,17 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs,
XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) ); XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) );
storage->procsClosure = procsClosure; storage->procsClosure = procsClosure;
if ( params->useHTTP ) {
storage->mainThread = pthread_self(); storage->mainThread = pthread_self();
pthread_mutex_init ( &storage->relayMutex, NULL );
pthread_cond_init( &storage->relayCondVar, NULL );
pthread_t thread;
(void)pthread_create( &thread, NULL, relayThread, storage );
pthread_detach( thread );
XP_ASSERT( XP_STRLEN(host) < VSIZE(storage->host) );
XP_MEMCPY( storage->host, host, XP_STRLEN(host) + 1 );
} else {
storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
(*procs->socketAdded)( storage, storage->socket, relaycon_receive ); (*procs->socketAdded)( storage, storage->socket, relaycon_receive );
@ -148,9 +184,7 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs,
storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) ); storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) );
storage->saddr.sin_port = htons(port); storage->saddr.sin_port = htons(port);
XP_ASSERT( XP_STRLEN(host) < VSIZE(storage->host) ); }
XP_MEMCPY( storage->host, host, XP_STRLEN(host) + 1 );
storage->params = params; storage->params = params;
storage->proto = XWPDEV_PROTO_VERSION_1; storage->proto = XWPDEV_PROTO_VERSION_1;
@ -311,6 +345,59 @@ onMainThread( RelayConStorage* storage )
return storage->mainThread = pthread_self(); return storage->mainThread = pthread_self();
} }
static void*
relayThread( void* arg )
{
RelayConStorage* storage = (RelayConStorage*)arg;
for ( ; ; ) {
pthread_mutex_lock( &storage->relayMutex );
while ( !storage->relayTaskList ) {
pthread_cond_wait( &storage->relayCondVar, &storage->relayMutex );
}
RelayTask* task = storage->relayTaskList->data;
storage->relayTaskList = storage->relayTaskList->next;
pthread_mutex_unlock( &storage->relayMutex );
switch ( task->typ ) {
case POST:
handlePost( task );
break;
case QUERY:
handleQuery( task );
break;
default:
XP_ASSERT(0);
}
}
return NULL;
}
static void
addTask( RelayConStorage* storage, RelayTask* task )
{
pthread_mutex_lock( &storage->relayMutex );
storage->relayTaskList = g_slist_append( storage->relayTaskList, task );
pthread_cond_signal( &storage->relayCondVar );
pthread_mutex_unlock( &storage->relayMutex );
}
static RelayTask*
makeRelayTask( RelayConStorage* storage, TaskType typ )
{
RelayTask* task = (RelayTask*)g_malloc0(sizeof(*task));
task->typ = typ;
task->storage = storage;
return task;
}
static void
freeRelayTask( RelayTask* task )
{
g_free( task->ws.ptr );
g_free( task );
}
static void static void
sendAckIf( RelayConStorage* storage, const MsgHeader* header ) sendAckIf( RelayConStorage* storage, const MsgHeader* header )
{ {
@ -331,6 +418,7 @@ process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead )
MsgHeader header; MsgHeader header;
if ( readHeader( &ptr, &header ) ) { if ( readHeader( &ptr, &header ) ) {
sendAckIf( storage, &header ); sendAckIf( storage, &header );
switch( header.cmd ) { switch( header.cmd ) {
case XWPDEV_REGRSP: { case XWPDEV_REGRSP: {
uint32_t len; uint32_t len;
@ -433,6 +521,7 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo
{ {
XP_ASSERT( 0 != (G_IO_IN & condition) ); /* FIX ME */ XP_ASSERT( 0 != (G_IO_IN & condition) ); /* FIX ME */
RelayConStorage* storage = (RelayConStorage*)data; RelayConStorage* storage = (RelayConStorage*)data;
XP_ASSERT( !storage->params->useHTTP );
XP_U8 buf[512]; XP_U8 buf[512];
struct sockaddr_in from; struct sockaddr_in from;
socklen_t fromlen = sizeof(from); socklen_t fromlen = sizeof(from);
@ -494,17 +583,18 @@ hostNameToIP( const XP_UCHAR* name )
typedef struct _PostArgs { typedef struct _PostArgs {
RelayConStorage* storage; RelayConStorage* storage;
WriteState ws; WriteState ws;
const XP_U8* msgbuf; XP_U8* msgbuf;
XP_U16 len; XP_U16 len;
} PostArgs; } PostArgs;
static gboolean static gboolean
onGotPostData(gpointer user_data) onGotPostData(gpointer user_data)
{ {
PostArgs* pa = (PostArgs*)user_data; RelayTask* task = (RelayTask*)user_data;
/* Now pull any data from the reply */ /* Now pull any data from the reply */
// got "{"status": "ok", "dataLen": 14, "data": "AYQDiDAyMUEzQ0MyADw=", "err": "none"}" // got "{"status": "ok", "dataLen": 14, "data": "AYQDiDAyMUEzQ0MyADw=", "err": "none"}"
json_object* reply = json_tokener_parse( pa->ws.ptr ); if ( !!task->ws.ptr ) {
json_object* reply = json_tokener_parse( task->ws.ptr );
json_object* replyData; json_object* replyData;
if ( json_object_object_get_ex( reply, "data", &replyData ) && !!replyData ) { if ( json_object_object_get_ex( reply, "data", &replyData ) && !!replyData ) {
int len = json_object_array_length(replyData); int len = json_object_array_length(replyData);
@ -513,25 +603,27 @@ onGotPostData(gpointer user_data)
const char* str = json_object_get_string( datum ); const char* str = json_object_get_string( datum );
gsize out_len; gsize out_len;
guchar* buf = g_base64_decode( (const gchar*)str, &out_len ); guchar* buf = g_base64_decode( (const gchar*)str, &out_len );
process( pa->storage, buf, out_len ); process( task->storage, buf, out_len );
g_free( buf ); g_free( buf );
} }
(void)json_object_put( replyData ); (void)json_object_put( replyData );
} }
(void)json_object_put( reply ); (void)json_object_put( reply );
}
g_free( pa->ws.ptr ); g_free( task->u.post.msgbuf );
g_free( pa );
freeRelayTask( task );
return FALSE; return FALSE;
} }
static void* static void
postThread( void* arg ) handlePost( RelayTask* task )
{ {
PostArgs* pa = (PostArgs*)arg; XP_LOGF( "%s(len=%d)", __func__, task->u.post.len );
XP_ASSERT( !onMainThread(pa->storage) ); XP_ASSERT( !onMainThread(task->storage) );
char* data = g_base64_encode( pa->msgbuf, pa->len ); char* data = g_base64_encode( task->u.post.msgbuf, task->u.post.len );
struct json_object* jobj = json_object_new_object(); struct json_object* jobj = json_object_new_object();
struct json_object* jstr = json_object_new_string(data); struct json_object* jstr = json_object_new_string(data);
g_free( data ); g_free( data );
@ -543,14 +635,14 @@ postThread( void* arg )
char url[128]; char url[128];
snprintf( url, sizeof(url), "%s://%s/xw4/relay.py/post", snprintf( url, sizeof(url), "%s://%s/xw4/relay.py/post",
RELAY_API_PROTO, pa->storage->host ); RELAY_API_PROTO, task->storage->host );
curl_easy_setopt( curl, CURLOPT_URL, url ); curl_easy_setopt( curl, CURLOPT_URL, url );
curl_easy_setopt( curl, CURLOPT_POST, 1L ); curl_easy_setopt( curl, CURLOPT_POST, 1L );
addJsonParams( curl, "params", jobj ); addJsonParams( curl, "params", jobj );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback ); curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &pa->ws ); curl_easy_setopt( curl, CURLOPT_WRITEDATA, &task->ws );
curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L ); curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L );
res = curl_easy_perform(curl); res = curl_easy_perform(curl);
@ -563,41 +655,32 @@ postThread( void* arg )
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
curl_global_cleanup(); curl_global_cleanup();
XP_LOGF( "%s(): got \"%s\"", __func__, pa->ws.ptr ); XP_LOGF( "%s(): got \"%s\"", __func__, task->ws.ptr );
// Put the data on the main thread for processing // Put the data on the main thread for processing
(void)g_idle_add( onGotPostData, pa ); (void)g_idle_add( onGotPostData, task );
} /* handlePost */
return NULL;
} /* postThread */
static ssize_t static ssize_t
post( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ) post( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len )
{ {
PostArgs* pa = (PostArgs*)g_malloc0(sizeof(*pa)); XP_LOGF( "%s(len=%d)", __func__, len );
pa->storage = storage; RelayTask* task = makeRelayTask( storage, POST );
pa->msgbuf = msgbuf; task->u.post.msgbuf = g_malloc(len);
pa->len = len; XP_MEMCPY( task->u.post.msgbuf, msgbuf, len );
task->u.post.len = len;
pthread_t thread; addTask( storage, task );
(void)pthread_create( &thread, NULL, postThread, (void*)pa );
pthread_detach( thread );
return len; return len;
} }
typedef struct _QueryArgs {
RelayConStorage* storage;
/* GSList* ids; */
WriteState ws;
GHashTable* map;
} QueryArgs;
static gboolean static gboolean
onGotQueryData( gpointer user_data ) onGotQueryData( gpointer user_data )
{ {
QueryArgs* qa = (QueryArgs*)user_data; RelayTask* task = (RelayTask*)user_data;
RelayConStorage* storage = task->storage;
XP_Bool foundAny = false; XP_Bool foundAny = false;
json_object* reply = json_tokener_parse( qa->ws.ptr ); if ( !!task->ws.ptr ) {
json_object* reply = json_tokener_parse( task->ws.ptr );
if ( !!reply ) { if ( !!reply ) {
CommsAddrRec addr = {0}; CommsAddrRec addr = {0};
addr_addType( &addr, COMMS_CONN_RELAY ); addr_addType( &addr, COMMS_CONN_RELAY );
@ -607,7 +690,7 @@ onGotQueryData( gpointer user_data )
int len1 = json_object_array_length( arrOfArrOfMoves ); int len1 = json_object_array_length( arrOfArrOfMoves );
XP_LOGF( "%s: got key: %s of len %d", __func__, relayID, len1 ); XP_LOGF( "%s: got key: %s of len %d", __func__, relayID, len1 );
if ( len1 > 0 ) { if ( len1 > 0 ) {
sqlite3_int64 rowid = *(sqlite3_int64*)g_hash_table_lookup( qa->map, relayID ); sqlite3_int64 rowid = *(sqlite3_int64*)g_hash_table_lookup( task->u.query.map, relayID );
for ( int ii = 0; ii < len1; ++ii ) { for ( int ii = 0; ii < len1; ++ii ) {
json_object* forGameArray = json_object_array_get_idx( arrOfArrOfMoves, ii ); json_object* forGameArray = json_object_array_get_idx( arrOfArrOfMoves, ii );
int len2 = json_object_array_length( forGameArray ); int len2 = json_object_array_length( forGameArray );
@ -616,7 +699,7 @@ onGotQueryData( gpointer user_data )
const char* asStr = json_object_get_string( oneMove ); const char* asStr = json_object_get_string( oneMove );
gsize out_len; gsize out_len;
guchar* buf = g_base64_decode( asStr, &out_len ); guchar* buf = g_base64_decode( asStr, &out_len );
(*qa->storage->procs.msgForRow)( qa->storage->procsClosure, &addr, (*storage->procs.msgForRow)( storage->procsClosure, &addr,
rowid, buf, out_len ); rowid, buf, out_len );
g_free(buf); g_free(buf);
foundAny = XP_TRUE; foundAny = XP_TRUE;
@ -626,25 +709,27 @@ onGotQueryData( gpointer user_data )
} }
json_object_put( reply ); json_object_put( reply );
} }
}
if ( foundAny ) { if ( foundAny ) {
/* Reschedule. If we got anything this time, check again sooner! */ /* Reschedule. If we got anything this time, check again sooner! */
reset_schedule_check_interval( qa->storage ); reset_schedule_check_interval( storage );
} }
schedule_next_check( qa->storage ); schedule_next_check( storage );
g_hash_table_destroy( qa->map ); g_hash_table_destroy( task->u.query.map );
g_free( qa ); freeRelayTask(task);
return FALSE; return FALSE;
} }
static void* static void
queryThread( void* arg ) handleQuery( RelayTask* task )
{ {
QueryArgs* qa = (QueryArgs*)arg; XP_ASSERT( !onMainThread(task->storage) );
XP_ASSERT( !onMainThread(qa->storage) );
GList* ids = g_hash_table_get_keys( qa->map ); if ( g_hash_table_size( task->u.query.map ) > 0 ) {
GList* ids = g_hash_table_get_keys( task->u.query.map );
json_object* jIds = json_object_new_array(); json_object* jIds = json_object_new_array();
for ( GList* iter = ids; !!iter; iter = iter->next ) { for ( GList* iter = ids; !!iter; iter = iter->next ) {
@ -659,19 +744,18 @@ queryThread( void* arg )
char url[128]; char url[128];
snprintf( url, sizeof(url), "%s://%s/xw4/relay.py/query", snprintf( url, sizeof(url), "%s://%s/xw4/relay.py/query",
RELAY_API_PROTO, qa->storage->host ); RELAY_API_PROTO, task->storage->host );
curl_easy_setopt(curl, CURLOPT_URL, url ); curl_easy_setopt(curl, CURLOPT_URL, url );
curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POST, 1L);
addJsonParams( curl, "ids", jIds ); addJsonParams( curl, "ids", jIds );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback ); curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &qa->ws ); curl_easy_setopt( curl, CURLOPT_WRITEDATA, &task->ws );
curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L ); curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L );
res = curl_easy_perform( curl ); res = curl_easy_perform( curl );
XP_LOGF( "%s(): curl_easy_perform() => %d", __func__, res );
/* Check for errors */ /* Check for errors */
if (res != CURLE_OK) { if (res != CURLE_OK) {
XP_LOGF( "curl_easy_perform() failed: %s", curl_easy_strerror(res)); XP_LOGF( "curl_easy_perform() failed: %s", curl_easy_strerror(res));
@ -680,13 +764,12 @@ queryThread( void* arg )
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
curl_global_cleanup(); curl_global_cleanup();
XP_LOGF( "%s(): got <<%s>>", __func__, qa->ws.ptr ); XP_LOGF( "%s(): got <<%s>>", __func__, task->ws.ptr );
}
/* Put processing back on the main thread */ /* Put processing back on the main thread */
g_idle_add( onGotQueryData, qa ); g_idle_add( onGotQueryData, task );
} /* handleQuery */
return NULL;
} /* queryThread */
static gboolean static gboolean
checkForMoves( gpointer user_data ) checkForMoves( gpointer user_data )
@ -695,23 +778,10 @@ checkForMoves( gpointer user_data )
RelayConStorage* storage = (RelayConStorage*)user_data; RelayConStorage* storage = (RelayConStorage*)user_data;
XP_ASSERT( onMainThread(storage) ); XP_ASSERT( onMainThread(storage) );
QueryArgs* qa = (QueryArgs*)g_malloc0(sizeof(*qa)); RelayTask* task = makeRelayTask( storage, QUERY );
qa->storage = storage;
sqlite3* dbp = storage->params->pDb; sqlite3* dbp = storage->params->pDb;
// qa->map = getRowsToRelayIDsMap( dbp ); task->u.query.map = getRelayIDsToRowsMap( dbp );
qa->map = getRelayIDsToRowsMap( dbp ); addTask( storage, task );
// qa->ids = g_hash_table_get_values( qa->map );
/* for ( GList* iter = values; !!iter; iter = iter->next ) { */
/* gpointer data = iter->data; */
/* XP_LOGF( "checkForMoves: got id: %s", (char*)data ); */
/* qa->ids = g_slist_prepend( qa->ids, g_strdup(data) ); */
/* } */
/* g_list_free( values ); */
pthread_t thread;
(void)pthread_create( &thread, NULL, queryThread, (void*)qa );
pthread_detach( thread );
schedule_next_check( storage ); schedule_next_check( storage );
return FALSE; return FALSE;