Skip to content

Instantly share code, notes, and snippets.

@markpapadakis
Created March 20, 2015 09:23
Show Gist options
  • Save markpapadakis/8dba5c480c13b12a056e to your computer and use it in GitHub Desktop.
Save markpapadakis/8dba5c480c13b12a056e to your computer and use it in GitHub Desktop.
Pseudo-code; thread accepting requests and processing them; thread does not block
struct filepagecache_warmer
: public coroutine
{
int fd;
const uint64_t offset;
const uint64_t len;
filepagecache_warmer(int _fd, const uint64_t _o, const uint64_t _l)
: fd(_fd), offset(_o), len(_l)
{
}
resume_res_t operator()(void)
{
BeginCoro();
SwitchFS::TouchFilePages(fd, offset, len);
EndCoro();
}
};
struct process_httpreq_coro
: public coroutine
{
URL *url;
http_request *req;
// members/vars used for the per-coro stack
// having a stack member and placing everything in there(scoped) helps
// with the programming mental model.
struct
{
int fd;
uint64_t fileSize;
} stack;
resume_res_t operator()(void)
{
BeginCoro();
if (req->method == "GET" && url->Path().Eq(_S("/cat.jpg"))
{
// contrived example
stack.fd = open("/tmp/cat.jpg", O_RDONLY);
stack.fileSize = lseek64(fd, 0, SEEK_END);
if (SwitchFS::InKernelCache(stack.fd, 0, stack.fileSize))
{
// Fast-path
// Resident in VMAs
}
else
{
// Someone else (another thread) does this
// We will wait for it, but will yield and run another runnable coro instead
WaitForCoro(new filepagecache_warmer(stack.fd, 0, stack.fileSize));
}
// Now we can safely assume thread won't block
auto *const buf = (uint8_t *)malloc(stack.fileSize);
(void)pread64(stack.fd, buf, stack.fileSize, 0);
(void)close(stack.fd);
// We could do it here, but why not have another coro do it
// and give other runnable coros a chance to run?
//
// ThisScheduler is a thread_local instance, we can use to enqueue new coros
// in this thread.
ThisScheduler.Schedule(new fileresponse_coro(buf, stack.fileSize, url, req));
}
EndCoro();
}
};
// The problem with blocking the thread, if it's the thread you are also performing network I/O (including
// accepting new requests etc), is that if you need to block to e.g read from disk, everything stalls, and
// you get crazy latency spikes.
// On the other hand, if you are going to use worker threads for processing parsed requests, you are
// losing performance because of queues contention (which can be really high), routing responses back
// to the network I/O thread for dispatching, etc.
// Ideally, you want threads that perform network I/O, execute the requests right there without having
// to forward them to other threads and incur that (often high) performance penalty, but you also
// need to make sure no request will block the thread.
// Using coros this way is an optimal way to do it.
void Thread::Run(void)
{
for (;;)
{
// accept() connections, read/write data, create new coros encapsulating
// incoming HTTP requests, whatever
ProcessNetworkIO();
// Be fair, don't run more than fairCounter coros here so that we 'll get back
// to processing network I/O as soon as possible.
uint32_t fairCounter{1024};
while (RunNextRunnable() && fairCounter)
--fairCounter;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment