Created
March 20, 2015 09:23
-
-
Save markpapadakis/8dba5c480c13b12a056e to your computer and use it in GitHub Desktop.
Pseudo-code; thread accepting requests and processing them; thread does not block
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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