Database

Published April 29, 2007
Advertisement
I downloaded the latest version of SQLite and stuck it inside a thread in my server. I then made log-in password validation be asynchronous, waiting for look-up in the database. That's better than using the hard-coded password "pass" that I've had until now :-)

Actually, the way I went about it might be interesting.

First, I have a common interface for "requester" and "someone requesting."

class IRequester {  public:    // Complete is called back by the Request when it's done.     // If "ok" is false, the error message is in "error".    // The request is auto-deleted after it's called Complete(),     // you do not call Dispose() after Complete().    virtual void Complete(bool ok, char const *error) = 0;};class IRequest {  public:    // Start the request running.    virtual void Start(IRequester *r) = 0;    // After Dispose() returns, Complete() will not be called     // for the request. Any work remaining to be done by the     // request will be canceled.    virtual void Dispose() = 0;};


This lets any asynchronous task (that still completes on the same thread) be managed in the same way, which makes helpers easy to write.

I also have the notion of something to do later, which I call a Task. This is useful for scheduled events, and for things like DPC (deferred procedure calls). I have two different task schedulers: one that just runs things as soon as the application gets up to the main loop, and one that runs tasks on a timed schedule.

class ITask {  public:    virtual bool Perform() = 0;};class IDeferredQueue {  public:    virtual IRequest *AddTask(ITask *task, FileLine fl) = 0;    virtual void RunTasks() = 0;    virtual void Dispose() = 0;};class IScheduler {  public:    virtual IRequest *AddScheduledTask(double atTime, ITask *task, FileLine fl) = 0;    IRequest *AddDelayedTask(double delay, ITask *task, FileLine fl) { return AddScheduledTask(Now()+delay, task, fl); }    virtual void Dispose() = 0;    virtual double Now() = 0;};


The Scheduler is actually implemented on top of the IDeferredQueue() (it keeps a task of its own that just reads the time and checks what's ready to run).

OK, so given this background, I then added a ThreadTaskQueue:

class IThreadTaskQueue {  public:    virtual void Dispose() = 0;    virtual IRequest *AddTask(ITask *task, FileLine fl) = 0;};


Given a task, it will hand that task off to a worker thread (owned by the queue), and call the Perform() of that task in the context of this other thread. After the task is done, it will call the requester back with completion status, in the main thread again. I implement the hand-off to the main thread using polling; as long as there is a thread task running, the ThreadTaskQueue will re-schedule itself using the DeferredQueue, and in each deferred callback, service any thread requests that are marked complete.

Cancellation is also a little special. Disposing the request you got from the ThreadTaskQueue ensures that Complete() is not called after the dispose. However, the work of the thread may currently be in flight, or may already have been completed and awaiting notification. Thus, you're not guaranteed that the work hasn't been done, but you're guaranteed that the work won't start if it hasn't already, and you're guaranteed that Complete() won't be called.

This structure reduces the risk of threading bugs, as creation, completion and cancellation all happens on the main thread. The only threaded data lives in the Task that gets handed off to the thread, which makes isolation easy.
Previous Entry The refund
Next Entry Bugs
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement