* Added command line options to allow telehttpd and telelogger to try to re-attach to shared memory.
* Added flags and version fields to telemetry struct.
* Improved error handling in several places.
* Some minor fixes and improvements.
.pause {
background-color:#fd0;
color : #000;
+ display: none;
}
.error {
background-color:#a00;
color : #fff;
+ display: none;
}
</style>
if (this.readyState == 4 && this.status == 200) {
var tele = JSON.parse(this.responseText);
+ //// pause and error bar status:
+
+ if ( !(tele.tele_flags & 1) ) { // TELE_FLAG_ALIVE
+ pausebar.style.display = "none";
+ errbar.style.display = "block";
+ errbar.innerHTML = "Game offline";
+ return;
+ }
+ else if ( tele.tele_version !== 1 ) { // TELE_VERSION
+ errbar.style.display = "block";
+ errbar.innerHTML = "Version mismatch";
+ } else {
+ errbar.style.display = "none";
+ }
+ pausebar.style.display = tele.paused ? "block" : "none";
+
//// "speedo" box
// speedometer:
source.innerHTML = tele.job_isvalid ? tele.job_source_city : '-';
destination.innerHTML = tele.job_isvalid ? tele.job_destination_city : '-';
reward.innerHTML = tele.job_isvalid ? tele.job_income + '.-' : '-';
-
- //// pause and error bar status:
-
- pausebar.style.display = tele.paused ? "block" : "none";
- errbar.style.display = "none";
}
else if (this.readyState == 4) {
// Show error status:
shmid = shmget(key, size, fl1);
if ( 0 > shmid ) {
- EPRINT( "shmget failed\n" );
+ EPRINT( "shmget: %s\n", strerror( errno ) );
return NULL;
}
+ DPRINT( "shmget ID is %d\n", shmid );
shmhnd = shmat(shmid, NULL, fl2);
if ( (void *)-1 == shmhnd ) {
- EPRINT( "shmat failed\n" );
+ EPRINT( "shmat: %s\n", strerror( errno ) );
return NULL;
}
return shmhnd;
}
+// Called by plugin to create the shared memory segment:
void *init_shmput(int key, size_t size)
{
return init_shm_( key, size, IPC_CREAT | IPC_EXCL | 0600, 0 );
}
+// Called by clients to attach to existing shared memory segment:
void *init_shmget(int key, size_t size) {
return init_shm_( key, size, 0, SHM_RDONLY );
}
shmid = shmget(key, 0, 0);
if ( 0 > shmid ) {
- EPRINT( "shmget failed\n" );
+ EPRINT( "shmget: %s\n", strerror( errno ) );
return -1;
}
- shmdt( p );
+ DPRINT( "shmget ID is %d\n", shmid );
+ // RMID will only succeed if we got the id via init_shmput()!
shmctl( shmid, IPC_RMID, NULL );
- return 0;
+ return shmdt( p );
}
size_t tele2json( char *buf, size_t size, const struct telemetry_state_t *tele ) {
size_t n = 0;
+ if ( NULL == tele )
+ return 0;
+
#define CHKSIZE do{ if ( n >= size ) return 0; }while(0)
CHKSIZE;
n += snprintf( buf+n, size-n, "{\n" ); CHKSIZE;
+ n += snprintf( buf+n, size-n, " \"tele_version\": %"PRIu32",\n", tele->version ); CHKSIZE;
+ n += snprintf( buf+n, size-n, " \"tele_flags\": %"PRIu32",\n", tele->flags ); CHKSIZE;
+
n += snprintf( buf+n, size-n, " \"game_id\": \"%s\",\n", tele->game_id ); CHKSIZE;
n += snprintf( buf+n, size-n, " \"game_major_ver\": %u,\n", tele->game_major_ver ); CHKSIZE;
n += snprintf( buf+n, size-n, " \"game_minor_ver\": %u,\n", tele->game_minor_ver ); CHKSIZE;
#include "tele2json.h"
static struct telemetry_state_t *telemetry;
+static struct telemetry_state_t telemetry0;
struct {
int port;
char *host;
char *origin;
char *idxfile;
+ bool retry;
} cfg = {
8837,
"*",
"localhost",
"http://localhost",
"index.html",
+ false,
+};
+
+struct thread_data_t {
+ int sock;
+ struct telemetry_state_t tele;
};
static volatile int force_quit;
}
}
-enum respond_code {
- r_index,
- r_json,
- r_none
-};
+static int init_shm( bool retry ) {
+ do {
+ DPRINT( "Trying to attach to shared memory ...\n" );
+ telemetry = init_shmget( TELE_SHM_KEY, sizeof *telemetry );
+ if ( NULL == telemetry ) {
+ if ( !retry )
+ return -1;
+ sleep(1);
+ }
+ else if ( TELE_VERSION != telemetry->version ) {
+ EPRINT( "telemetry version mismatch: got %u, want %u\n",
+ (unsigned)telemetry->version, TELE_VERSION );
+ release_shm( TELE_SHM_KEY, telemetry );
+ telemetry = NULL;
+ if ( !retry )
+ return -1;
+ sleep(1);
+ }
+ if ( force_quit )
+ return -1;
+ } while ( NULL == telemetry );
+ DPRINT( "have telemetry version %u\n", (unsigned)telemetry->version );
+ return 0;
+}
-static void respond(int fd, const char *req, int code) {
+static int check_shm( bool retry ) {
+ if ( NULL == telemetry )
+ return init_shm( retry );
+ if ( !(telemetry->flags & TELE_FLAG_ALIVE) ) {
+ release_shm( TELE_SHM_KEY, telemetry );
+ telemetry = NULL;
+ return init_shm( retry );
+ }
+ return 0;
+}
+
+static void respond( struct thread_data_t *td, const char *req ) {
char buf[4096];
char origin[256];
char host[256];
const char *s;
+ int fd = td->sock;
+ enum respond_code {
+ r_index,
+ r_json,
+ r_none
+ } code;
+
+ if ( 0 == strncmp( req, "GET /json HTTP", 14 ) )
+ code = r_json;
+ else if ( 0 == strncmp( req, "GET / HTTP", 10 ) )
+ code = r_index;
+ else
+ code = r_none;
switch (code) {
case r_json:
write(fd, buf, sprintf(buf, "Access-Control-Allow-Origin: %s\r\n", origin));
write(fd, buf, sprintf(buf, "Content-type: text/json\r\n"));
write(fd, buf, sprintf(buf, "Connection: close\r\n\r\n"));
- write( fd, buf, tele2json(buf, sizeof buf, telemetry) );
+ write( fd, buf, tele2json(buf, sizeof buf, &td->tele) );
}
else {
struct file_info_t fi;
}
static void *handle_conn( void *p ) {
- int sock = *(int*)p;
+ struct thread_data_t *td = p;
char buf[2048];
- DPRINT( "sock: %d\n", sock );
- if ( 0 == rcv_request(sock, buf, sizeof buf, 1000) ) {
+ DPRINT( "sock: %d\n", td->sock );
+ if ( 0 == rcv_request(td->sock, buf, sizeof buf, 1000) ) {
DPRINT( "request:\n%s", buf);
- if (0 == strncmp(buf, "GET /json HTTP", 10))
- respond(sock, buf, r_json);
- else if (0 == strncmp(buf, "GET / HTTP", 10))
- respond(sock, buf, r_index);
- else
- respond(sock, NULL, r_none);
+ respond( td, buf );
}
- net_close( sock );
- free( p );
+ net_close( td->sock );
+ free( td );
return NULL;
}
static int serve_http(void) {
int as, ss;
- int *ps;
+ struct thread_data_t *td;
pthread_t tid;
pthread_attr_t attr;
}
while ( !force_quit ) {
- if ( 0 < (as = net_accept( ss )) && NULL != (ps = malloc( sizeof *ps )) ) {
- *ps = as;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&tid, &attr, handle_conn, ps);
+ as = net_accept( ss );
+ if ( 0 != check_shm( false ) ) {
+ if ( !cfg.retry )
+ return -1;
+ }
+ if ( 0 < as ) {
+ if ( NULL != (td = malloc( sizeof *td )) ) {
+ td->sock = as;
+ memcpy( &td->tele, telemetry ? telemetry : &telemetry0, sizeof td->tele );
+ pthread_attr_init( &attr );
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
+ if ( 0 != pthread_create( &tid, &attr, handle_conn, td ) ) {
+ net_close( td->sock );
+ free( td );
+ }
+ }
+ else {
+ EPRINT( "malloc: %s\n", strerror(errno) );
+ net_close( as );
+ }
}
}
" -i file file to serve as index file; default: index.html\n"
" -o str origin; default: http://localhost\n"
" -p N TCP listen port; default: 8837\n"
+ " -r try to (re-)attach to shared memory\n"
);
}
static int config( int argc, char *argv[] ) {
int opt;
- const char *ostr = "+b:h:i:o:p:";
+ const char *ostr = "+rb:h:i:o:p:";
while ( -1 != ( opt = getopt( argc, argv, ostr ) ) ) {
switch ( opt ) {
case 'p':
cfg.port = strtol(optarg, NULL, 10);
break;
+ case 'r':
+ cfg.retry = true;
+ break;
case ':':
EPRINT( "Missing argument for option '%c'\n", optopt );
usage( argv[0] );
int main(int argc, char *argv[]) {
signal(SIGTERM, handle_signal);
signal(SIGINT, handle_signal);
+ // We don't want do get killed by some thread writing to a stale socket:
+ signal(SIGPIPE, SIG_IGN);
if ( 0 != config( argc, argv ) )
exit( EXIT_FAILURE);
- if ( NULL == ( telemetry = init_shmget( TELE_SHM_KEY, sizeof *telemetry ) ) )
- exit( EXIT_FAILURE);
-
if ( 0 != serve_http() )
exit( EXIT_FAILURE);
int count;
int delay;
bool pause;
+ bool retry;
} cfg = {
0,
1000,
true,
+ false,
};
static void msleep( int ms ) {
usleep( 999 );
}
+static int init_shm( bool retry ) {
+ do {
+ DPRINT( "Trying to attach to shared memory ...\n" );
+ telemetry = init_shmget( TELE_SHM_KEY, sizeof *telemetry );
+ if ( NULL == telemetry ) {
+ if ( !retry )
+ return -1;
+ sleep(1);
+ }
+ else if ( TELE_VERSION != telemetry->version ) {
+ DPRINT( "telemetry version mismatch: got %u, want %u\n",
+ (unsigned)telemetry->version, TELE_VERSION );
+ if ( !retry )
+ return -1;
+ release_shm( TELE_SHM_KEY, telemetry );
+ telemetry = NULL;
+ sleep(1);
+ }
+ } while ( NULL == telemetry );
+
+ return 0;
+}
+
+static int check_shm( bool retry ) {
+ if ( NULL == telemetry )
+ return init_shm( retry );
+ if ( !(telemetry->flags & TELE_FLAG_ALIVE) ) {
+ release_shm( TELE_SHM_KEY, telemetry );
+ telemetry = NULL;
+ return init_shm( retry );
+ }
+ return 0;
+}
+
static int log_console( int cnt, int delay ) {
char buf[4096];
bool forever = !cnt;
uint64_t last_timestamp = telemetry->timestamp;
while ( forever || cnt-- > 0 ) {
+ if ( 0 != check_shm( cfg.retry ) )
+ return -1;
+
if ( tele2json( buf, sizeof buf, telemetry ) > 0 )
puts( buf );
if ( forever || cnt > 0 ) {
- if ( last_paused && cfg.pause ) {
- while ( telemetry->paused )
+ if ( last_paused && telemetry->paused && cfg.pause ) {
+ while ( telemetry->paused ) {
+ if ( 0 != check_shm( cfg.retry ) )
+ return -1;
usleep( 10000 );
+ }
}
- else if ( last_timestamp == telemetry->timestamp && cfg.pause ) {
- while ( last_timestamp == telemetry->timestamp )
+ else if ( last_timestamp == telemetry->timestamp && cfg.pause ) {
+ while ( last_timestamp == telemetry->timestamp ) {
+ if ( 0 != check_shm( cfg.retry ) )
+ return -1;
msleep( delay );
+ }
}
else
msleep( delay );
" -h this help page\n"
" -n N number of cycles; default: 0 (unlimited)\n"
" -p ignore pause condition\n"
+ " -r try to (re-)attach to shared memory\n"
);
}
static int config( int argc, char *argv[] ) {
int opt;
- const char *ostr = "+hpd:n:";
+ const char *ostr = "+hprd:n:";
while ( -1 != ( opt = getopt( argc, argv, ostr ) ) ) {
switch ( opt ) {
case 'p':
cfg.pause = false;
break;
+ case 'r':
+ cfg.retry = true;
+ break;
case ':':
EPRINT( "Missing argument for option '%c'\n", optopt );
usage( argv[0] );
int main(int argc, char *argv[]) {
if ( 0 != config( argc, argv ) )
exit( EXIT_FAILURE);
- if ( NULL == ( telemetry = init_shmget( TELE_SHM_KEY, sizeof *telemetry ) ) )
+ if ( 0 != init_shm( cfg.retry ) )
exit( EXIT_FAILURE);
if ( 0 != log_console( cfg.count, cfg.delay ) )
exit( EXIT_FAILURE);
#define TELE_STRLEN 30
+#define TELE_VERSION 1
+
+#define TELE_FLAG_ALIVE 0x0001U
+
struct telemetry_state_t {
+ uint32_t version;
+ uint32_t flags;
+
char game_id[10];
unsigned game_major_ver;
unsigned game_minor_ver;
p = init_shmput( TELE_SHM_KEY, sizeof *telemetry );
telemetry = static_cast<struct telemetry_state_t *>(p);
}
- assert( NULL != telemetry );
+ if ( NULL != telemetry ) {
+ telemetry->version = TELE_VERSION;
+ telemetry->flags |= TELE_FLAG_ALIVE;
+ }
return NULL != telemetry;
}
static void drop_shm(void)
{
if ( NULL != telemetry ) {
+ telemetry->flags &= ~TELE_FLAG_ALIVE;
release_shm( TELE_SHM_KEY, telemetry );
telemetry = NULL;
}