CXX = gcc
FLAGS = -std=c99 -Wall -Werror -pedantic -g
LIB = -lpthread -lfcgi -lssl
-OBJ = log.o sensor.o fastcgi.o main.o
+OBJ = log.o sensor.o fastcgi.o thread.o main.o
RM = rm -f
BIN = server
+
$(BIN) : $(OBJ)
$(CXX) $(FLAGS) -o $(BIN) $(OBJ) $(LIB)
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
+#include <sys/time.h>
#include "log.h"
#include "fastcgi.h"
+#include "thread.h"
#endif //_COMMON_H
#include <time.h>
#include "common.h"
-#include "fastcgi.h"
#include "sensor.h"
#include "log.h"
+#include "options.h"
#define LOGIN_TIMEOUT 180
printf("{\r\n");
printf("\t\"module\" : \"%s\"", context->current_module);
FCGI_JSONLong("status", status_code);
+
+ // Jeremy: Should we include a timestamp in the JSON; something like this?
+ double start_time = g_options.start_time.tv_sec + 1e-6*(g_options.start_time.tv_usec);
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ double current_time = now.tv_sec + 1e-6*(now.tv_usec);
+ FCGI_JSONDouble("start_time", start_time);
+ FCGI_JSONDouble("current_time", current_time);
+ FCGI_JSONDouble("running_time", current_time - start_time);
+
}
/**
/**
* Main FCGI request loop that receives/responds to client requests.
* @param data Reserved.
+ * @returns NULL (void* required for consistency with pthreads, although at the moment this runs in the main thread anyway)
+ * TODO: Get this to exit with the rest of the program!
*/
-void FCGI_RequestLoop (void *data)
+void * FCGI_RequestLoop (void *data)
{
FCGIContext context = {0};
+ Log(LOGDEBUG, "First request...");
+ //TODO: The FCGI_Accept here is blocking.
+ // That means that if another thread terminates the program, this thread
+ // will not terminate until the next request is made.
while (FCGI_Accept() >= 0) {
+
+ if (Thread_Runstate() != RUNNING)
+ {
+ //TODO: Yeah... deal with this better :P
+ Log(LOGERR, "FIXME; FCGI gets request after other threads have finished.");
+ printf("Content-type: text/plain\r\n\r\n+++OUT OF CHEESE ERROR+++\n");
+ break;
+ }
+
+ Log(LOGDEBUG, "Got request #%d", context.response_number);
ModuleHandler module_handler = NULL;
char module[BUFSIZ], params[BUFSIZ];
strncat(module, " [unknown]", BUFSIZ);
FCGI_RejectJSON(&context);
}
-
context.response_number++;
+
+ Log(LOGDEBUG, "Waiting for request #%d", context.response_number);
}
+
+ Log(LOGDEBUG, "Thread exiting.");
+ Thread_QuitProgram(false);
+ // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return.
+ return NULL;
+
+
}
extern void FCGI_JSONValue(const char *format, ...);
extern void FCGI_EndJSON();
extern void FCGI_RejectJSON(FCGIContext *context);
-extern void FCGI_RequestLoop (void *data);
+extern void * FCGI_RequestLoop (void *data);
#endif
// --- Function implementations --- //
+//TODO: Migrate to syslog (shouldn't be too hard; these functions basically do what syslog does)
+// Note that we will want to have a seperate log as well as syslog; give the user the option to view the log using the GUI
+
/**
* Print a message to stderr
* @param level - Specify how severe the message is.
// --- Variable definitions --- //
Options g_options; // options passed to program through command line arguments
-Sensor g_sensors[NUMSENSORS]; // sensors array
// --- Function definitions --- //
{
g_options.program = argv[0]; // program name
g_options.verbosity = LOGDEBUG; // default log level
+ gettimeofday(&(g_options.start_time), NULL); // Start time
Log(LOGDEBUG, "Called as %s with %d arguments.", g_options.program, argc);
}
*/
//TODO: Something that gets massively annoying with threads is that you can't predict which one gets the signal
// There are ways to deal with this, but I can't remember them
+// Probably sufficient to just call Thread_QuitProgram here
void SignalHandler(int signal)
{
// At the moment just always exit.
// Call `exit` so that Cleanup will be called to... clean up.
Log(LOGWARN, "Got signal %d (%s). Exiting.", signal, strsignal(signal));
- exit(signal);
+ Thread_QuitProgram(false);
+ //exit(signal);
}
/**
* @param argc - Num args
* @param argv - Args
* @returns 0 on success, error code on failure
+ * NOTE: NEVER USE exit(3)! Instead call Thread_QuitProgram
*/
int main(int argc, char ** argv)
{
ParseArguments(argc, argv);
- // start sensor threads
- for (int i = 0; i < NUMSENSORS; ++i)
+ // signal handler
+ //TODO: Make this work
+ /*
+ int signals[] = {SIGINT, SIGSEGV, SIGTERM};
+ for (int i = 0; i < sizeof(signals)/sizeof(int); ++i)
{
- Sensor_Init(g_sensors+i, i);
- pthread_create(&(g_sensors[i].thread), NULL, Sensor_Main, (void*)(g_sensors+i));
+ signal(signals[i], SignalHandler);
}
+ */
+ Sensor_Spawn();
// run request thread in the main thread
FCGI_RequestLoop(NULL);
+
+ // Join the dark side, Luke
+ // *cough*
+ // Join the sensor threads
+ Sensor_Join();
+ Cleanup();
return 0;
}
const char * program;
/** Determines at what level log messages are shown **/
int verbosity;
+ /** Time at which program begins to run **/
+ struct timeval start_time;
+ /** Time at which program exits **/
+ struct timeval end_time;
} Options;
#!/bin/bash
# Use this to quickly test run the server in valgrind
-spawn-fcgi -p9005 -n /usr/bin/valgrind ./server
+#spawn-fcgi -p9005 -n ./valgrind.sh
+# Use this to run the server normally
+spawn-fcgi -p9005 -n ./server
#include "sensor.h"
#include <math.h>
+/** Array of sensors, initialised by Sensor_Init **/
+static Sensor g_sensors[NUMSENSORS]; //global to this file
+
/**
* Read a data value from a sensor; block until value is read
* @param sensor_id - The ID of the sensor
- * @returns The current value of the sensor
+ * @param d - DataPoint to set
+ * @returns NULL on error, otherwise d
*/
-DataPoint GetData(int sensor_id)
+DataPoint * GetData(int sensor_id, DataPoint * d)
{
// switch based on the sensor_id at the moment for testing;
// might be able to just directly access ADC from sensor_id?
//TODO: Implement for real sensors
- DataPoint d;
- //TODO: Deal with time stamps properly
- static int count = 0;
- d.time = count++;
+
+ //TODO: We should ensure the time is *never* allowed to change on the server if we use gettimeofday
+ // Another way people might think of getting the time is to count CPU cycles with clock()
+ // But this will not work because a) CPU clock speed may change on some devices (RPi?) and b) It counts cycles used by all threads
+ gettimeofday(&(d->time_stamp), NULL);
+
switch (sensor_id)
{
case SENSOR_TEST0:
- d.value = count;
+ {
+ static int count = 0;
+ d->value = count++;
break;
+ }
case SENSOR_TEST1:
- d.value = (float)(rand() % 100) / 100;
+ d->value = (float)(rand() % 100) / 100;
break;
default:
Fatal("Unknown sensor id: %d", sensor_id);
break;
}
usleep(100000); // simulate delay in sensor polling
+
return d;
}
* Initialise a sensor
* @param s - Sensor to initialise
*/
-void Sensor_Init(Sensor * s, int id)
+void Init(Sensor * s, int id)
{
s->write_index = 0;
s->read_offset = 0;
s->id = id;
- #define FILENAMESIZE BUFSIZ
+ #define FILENAMESIZE 3
char filename[FILENAMESIZE];
- //if (s->id >= pow(10, FILENAMESIZE))
- if (false)
+ if (s->id >= pow(10, FILENAMESIZE))
{
Fatal("Too many sensors! FILENAMESIZE is %d; increase it and recompile.", FILENAMESIZE);
}
}
+
+
+
/**
* Run the main sensor polling loop
* @param arg - Cast to Sensor* - Sensor that the thread will handle
{
Sensor * s = (Sensor*)(arg);
- while (true) //TODO: Exit condition
+ while (Thread_Runstate() == RUNNING) //TODO: Exit condition
{
// The sensor will write data to a buffer until it is full
// Then it will open a file and dump the buffer to the end of it.
while (s->write_index < SENSOR_DATABUFSIZ)
{
- s->buffer[s->write_index] = GetData(s->id);
+ DataPoint * d = &(s->buffer[s->write_index]);
+ if (GetData(s->id, d) == NULL)
+ {
+ Fatal("Error collecting data");
+ }
s->write_index += 1;
}
// CRITICAL SECTION (no threads should be able to read/write the file at the same time)
pthread_mutex_lock(&(s->mutex));
+ //TODO: Valgrind complains about this fseek: "Syscall param write(buf) points to uninitialised byte(s)"
+ // Not sure why, but we should find out and fix it.
fseek(s->file, 0, SEEK_END);
int amount_written = fwrite(s->buffer, sizeof(DataPoint), SENSOR_DATABUFSIZ, s->file);
if (amount_written != SENSOR_DATABUFSIZ)
s->write_index = 0; // reset position in buffer
}
- return NULL;
+ Log(LOGDEBUG, "Thread for sensor %d exits", s->id);
+ return NULL;
}
/**
return amount_read;
}
+/**
+ * Get a Sensor given an ID string
+ * @param id_str ID string
+ * @returns Sensor* identified by the string; NULL on error
+ */
+Sensor * Sensor_Identify(const char * id_str)
+{
+ char * end;
+ // Parse string as integer
+ int id = strtol(id_str, &end, 10);
+ if (*end != '\0')
+ {
+ return NULL;
+ }
+ // Bounds check
+ if (id < 0 || id > NUMSENSORS)
+ return NULL;
+
+ return g_sensors+id;
+}
+
/**
* Handle a request to the sensor module
* @param context - The context to work in
StatusCodes status = STATUS_OK;
const char * key; const char * value;
- int sensor_id = SENSOR_NONE;
+ Sensor * sensor = NULL;
while ((params = FCGI_KeyPair(params, &key, &value)) != NULL)
{
Log(LOGDEBUG, "Got key=%s and value=%s", key, value);
if (strcmp(key, "id") == 0)
{
- char *end;
- if (sensor_id != SENSOR_NONE)
+ if (sensor != NULL)
{
Log(LOGERR, "Only one sensor id should be specified");
status = STATUS_ERROR;
status = STATUS_ERROR;
break;
}
- //TODO: Use human readable sensor identifier string for API?
- sensor_id = strtol(value, &end, 10);
- if (*end != '\0')
+
+ sensor = Sensor_Identify(value);
+ if (sensor == NULL)
{
- Log(LOGERR, "Sensor id not an integer; %s", value);
+ Log(LOGERR, "Invalid sensor id: %s", value);
status = STATUS_ERROR;
break;
}
}
}
- if (sensor_id == SENSOR_NONE)
- {
- Log(LOGERR, "No sensor id specified");
- status = STATUS_ERROR;
- }
- else if (sensor_id >= NUMSENSORS || sensor_id < 0)
+ if (status != STATUS_ERROR && sensor == NULL)
{
- Log(LOGERR, "Invalid sensor id %d", sensor_id);
+ Log(LOGERR, "No valid sensor id given");
status = STATUS_ERROR;
}
}
else
{
+
FCGI_BeginJSON(context, status);
FCGI_JSONPair(key, value); // should spit back sensor ID
//Log(LOGDEBUG, "Call Sensor_Query...");
- int amount_read = Sensor_Query(&(g_sensors[sensor_id]), buffer, SENSOR_QUERYBUFSIZ);
+ int amount_read = Sensor_Query(sensor, buffer, SENSOR_QUERYBUFSIZ);
//Log(LOGDEBUG, "Read %d DataPoints", amount_read);
//Log(LOGDEBUG, "Produce JSON response");
FCGI_JSONKey("data");
FCGI_JSONValue("[");
for (int i = 0; i < amount_read; ++i)
{
- FCGI_JSONValue("[%f, %f]", buffer[i].time, buffer[i].value);
+ //TODO: Consider; is it better to give both tv_sec and tv_usec to the client seperately, instead of combining here?
+ //NOTE: Must always use doubles; floats get rounded!
+ double time = buffer[i].time_stamp.tv_sec + 1e-6*(buffer[i].time_stamp.tv_usec);
+ FCGI_JSONValue("[%f, %f]", time, buffer[i].value);
if (i+1 < amount_read)
FCGI_JSONValue(",");
}
FCGI_EndJSON();
}
}
+
+/**
+ * Setup Sensors, start Sensor polling thread(s)
+ */
+void Sensor_Spawn()
+{
+ // start sensor threads
+ for (int i = 0; i < NUMSENSORS; ++i)
+ {
+ Init(g_sensors+i, i);
+ pthread_create(&(g_sensors[i].thread), NULL, Sensor_Main, (void*)(g_sensors+i));
+ }
+}
+
+/**
+ * Quit Sensor loops
+ */
+void Sensor_Join()
+{
+ if (!Thread_Runstate())
+ {
+ Fatal("This function should not be called before Thread_QuitProgram");
+ }
+ for (int i = 0; i < NUMSENSORS; ++i)
+ {
+ pthread_join(g_sensors[i].thread, NULL);
+ Destroy(g_sensors+i);
+ }
+}
typedef struct
{
/** Time at which data was taken **/
- float time;
+ struct timeval time_stamp; //TODO: Consider; use float instead?
/** Value of data **/
float value;
} DataPoint;
typedef struct
{
/** ID number of the sensor **/
- enum {SENSOR_TEST0=0, SENSOR_TEST1=1, SENSOR_NONE} id;
+ enum {SENSOR_TEST0=0, SENSOR_TEST1=1} id;
/** Buffer to store data from the sensor **/
DataPoint buffer[SENSOR_DATABUFSIZ];
/** Index of last point written in the data buffer **/
} Sensor;
-/** Array of Sensors **/
-extern Sensor g_sensors[];
-extern void Sensor_Init(Sensor * s, int id); // Initialise sensor
+
+extern void Sensor_Spawn(); // Initialise sensor
+extern void Sensor_Join(); //Join sensor threads
extern void * Sensor_Main(void * args); // main loop for sensor thread; pass a Sensor* cast to void*
extern int Sensor_Query(Sensor * s, DataPoint * buffer, int bufsiz); // fill buffer with sensor data
--- /dev/null
+/**
+ * @file thread.c
+ * @purpose Implementation of thread control
+ */
+
+#include "thread.h"
+#include "options.h"
+
+pthread_mutex_t mutex_runstate = PTHREAD_MUTEX_INITIALIZER;
+Runstate runstate = RUNNING;
+
+/**
+ * Set the runstate, causing all threads to exit when they next check Thread_Runstate
+ * Repeated calls to this function have no effect on the runstate.
+ * @param error - Set to true to indicate an error occured
+ */
+void Thread_QuitProgram(bool error)
+{
+ if (runstate != RUNNING)
+ {
+ Log(LOGNOTE, "Called when program is not running; runstate = %d", runstate);
+ return;
+ }
+
+
+ Log(LOGNOTE, "Program will quit; error = %d", (int)error);
+
+ //CRITICAL SECTION - We do NOT want multiple threads editing the runstate at the same time!
+ pthread_mutex_lock(&mutex_runstate);
+ if (error)
+ runstate = QUIT_ERROR;
+ else
+ runstate = QUIT;
+
+ gettimeofday(&g_options.end_time, NULL);
+ pthread_mutex_unlock(&mutex_runstate);
+ // End critical section
+}
+
+/**
+ * Check the runstate; to be called periodically by all threads.
+ * This function will call Thread_QuitProgram and change the Runstate there is an exit condition detected.
+ */
+Runstate Thread_Runstate()
+{
+ //TODO: Add real exit conditions; for testing purposes, set a timeout
+ /*
+ struct timeval time;
+ gettimeofday(&time, NULL);
+ Log(LOGDEBUG, "It has been %d seconds since program started.", time.tv_sec - g_options.start_time.tv_sec);
+ if (time.tv_sec - g_options.start_time.tv_sec > 3)
+ {
+ Thread_QuitProgram(false);
+ }
+ */
+
+ // Just reading the runstate doesn't really require a mutex
+ // The worst case: Another thread alters the runstate before this thread gets the result; this thread thinks the program is still running
+ // In that case, the thread will run an extra cycle of its loop and *then* exit. Since the runstate can only be changed once.
+ // We could put a mutex here anyway, but it will have an impact on how fast the loops can run.
+ return runstate;
+}
--- /dev/null
+/**
+ * @file thread.h
+ * @purpose Declarations for thread control related functions and variables
+ */
+
+#ifndef _THREAD_H
+#define _THREAD_H
+
+#include "common.h"
+#include <pthread.h>
+
+typedef enum {QUIT, QUIT_ERROR, RUNNING} Runstate;
+
+/** Determine if the thread should exit; to be called periodically **/
+extern Runstate Thread_Runstate();
+/** Tell all other threads (when they call Thread_ExitCondition) to exit. Repeated calls have no effect. **/
+extern void Thread_QuitProgram(bool error);
+
+#endif //_THREAD_H
+
+//EOF
--- /dev/null
+#!/bin/bash
+valgrind --leak-check=full ./server