Loop::defer and almost working async streaming
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
|
||||
/* This is just a very simple and inefficient demo of async responses,
|
||||
* please do roll your own variant or use a database or Node.js's async
|
||||
@@ -11,7 +12,8 @@ struct AsyncFileReader {
|
||||
private:
|
||||
/* The cache we have in memory for this file */
|
||||
std::string cache;
|
||||
int cacheOffset = 0;
|
||||
int cacheOffset;
|
||||
bool hasCache;
|
||||
|
||||
/* The pending async file read (yes we only support one pending read) */
|
||||
std::function<void(std::string_view)> pendingReadCb;
|
||||
@@ -38,6 +40,7 @@ public:
|
||||
fin.seekg(0, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
cacheOffset = 0;
|
||||
hasCache = true;
|
||||
|
||||
// get loop for thread
|
||||
|
||||
@@ -47,7 +50,7 @@ public:
|
||||
/* Returns any data already cached for this offset */
|
||||
std::string_view peek(int offset) {
|
||||
/* Did we hit the cache? */
|
||||
if (offset >= cacheOffset && ((offset - cacheOffset) < cache.length())) {
|
||||
if (hasCache && offset >= cacheOffset && ((offset - cacheOffset) < cache.length())) {
|
||||
/* Cache hit */
|
||||
//std::cout << "Cache hit!" << std::endl;
|
||||
return std::string_view(cache.data() + offset - cacheOffset, cache.length() - offset + cacheOffset);
|
||||
@@ -61,14 +64,39 @@ public:
|
||||
/* Asynchronously request more data at offset */
|
||||
void request(int offset, std::function<void(std::string_view)> cb) {
|
||||
|
||||
// std::async this
|
||||
//std::cout << "Caching 1 MB at offset = " << offset << std::endl;
|
||||
fin.seekg(offset, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
cacheOffset = offset;
|
||||
// in this case, what do we do?
|
||||
// we need to queue up this chunk request and callback!
|
||||
// if queue is full, either block or close the connection via abort!
|
||||
if (!hasCache) {
|
||||
// already requesting a chunk!
|
||||
std::cout << "ERROR: already requesting a chunk!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop.defer this
|
||||
cb(std::string_view(cache.data(), cache.length()));
|
||||
// disable cache
|
||||
hasCache = false;
|
||||
|
||||
std::async(std::launch::async, [this, cb, offset]() {
|
||||
std::cout << "ASYNC Caching 1 MB at offset = " << offset << std::endl;
|
||||
|
||||
|
||||
|
||||
fin.seekg(offset, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
cacheOffset = offset;
|
||||
|
||||
loop->defer([this, cb]() {
|
||||
|
||||
int chunkSize = std::min(cache.length(), fileSize - offset);
|
||||
|
||||
if (chunkSize != cache.length()) {
|
||||
std::cout << "LESS THAN A CACHE 1 MB!" << std::endl;
|
||||
}
|
||||
|
||||
hasCache = true;
|
||||
cb(std::string_view(cache.data(), chunkSize));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Abort any pending async. request */
|
||||
|
||||
@@ -43,10 +43,12 @@ int main(int argc, char **argv) {
|
||||
return std::pair<bool, std::string_view>(false, chunk);
|
||||
} else {
|
||||
|
||||
std::string_view outerChunk;
|
||||
//std::string_view outerChunk;
|
||||
|
||||
/* We had nothing readily available right now, request async chunk and pause the stream until we have */
|
||||
asyncFileReader.request(offset, [&outerChunk, res](std::string_view chunk) {
|
||||
asyncFileReader.request(offset, [res](std::string_view chunk) {
|
||||
|
||||
std::cout << "We came here!" << std::endl;
|
||||
|
||||
/* We were aborted */
|
||||
if (!chunk.length()) {
|
||||
@@ -55,13 +57,12 @@ int main(int argc, char **argv) {
|
||||
// we need a way to NOT resume a paused socket! essentially close!
|
||||
} else {
|
||||
/* We finally got the data, resume stream with this chunk */
|
||||
//res->resume(chunk);
|
||||
outerChunk = chunk;
|
||||
res->resume(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
std::cout << "Returning chunk of size: " << outerChunk.length() << std::endl;
|
||||
return std::pair<bool, std::string_view>(false, outerChunk);
|
||||
//std::cout << "Returning chunk of size: " << outerChunk.length() << std::endl;
|
||||
//return std::pair<bool, std::string_view>(false, outerChunk);
|
||||
|
||||
// what if we resumed before we paused! we cannot do that!
|
||||
|
||||
|
||||
@@ -168,6 +168,13 @@ private:
|
||||
// todo: share this path with HttpResponse::write (it is exatly the same logic!)
|
||||
while (true) {
|
||||
auto [msg_more, chunk] = httpResponseData->outStream(httpResponseData->offset);
|
||||
|
||||
if (chunk.length() == 0) {
|
||||
std::cout << "onwritable paused!" << std::endl;
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT;
|
||||
break;
|
||||
}
|
||||
|
||||
int written = asyncSocket->mergeDrain(chunk);
|
||||
httpResponseData->offset += written;
|
||||
// this is not correct, we can reach the end!
|
||||
|
||||
+28
-1
@@ -96,6 +96,7 @@ public:
|
||||
|
||||
/* Do nothing if not even paused */
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT)) {
|
||||
std::cout << "Resue called but we are not even in paused state!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,11 +105,33 @@ public:
|
||||
/* Remove paused status */
|
||||
httpResponseData->state &= ~HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT;
|
||||
|
||||
if (chunk.length()) {
|
||||
/*if (chunk.length()) {
|
||||
int written = AsyncSocket<SSL>::write(chunk.data(), chunk.length(), true);
|
||||
|
||||
if (written == chunk.length()) {
|
||||
// pull a new chunk from the callback (basically call onWritable)
|
||||
std::cout << "Wrote everything off!" << std::endl;
|
||||
}
|
||||
}*/
|
||||
|
||||
AsyncSocket<SSL> *asyncSocket = this;
|
||||
|
||||
// again, this path is shared with onwritable, write and here!
|
||||
while (true) {
|
||||
auto [msg_more, chunk] = httpResponseData->outStream(httpResponseData->offset);
|
||||
|
||||
// break on pause!
|
||||
if (chunk.length() == 0) {
|
||||
std::cout << "Resume paused!" << std::endl;
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT;
|
||||
break;
|
||||
}
|
||||
|
||||
int written = asyncSocket->mergeDrain(chunk);
|
||||
httpResponseData->offset += written;
|
||||
// this is not correct, we can reach the end!
|
||||
if (written < chunk.length()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +187,10 @@ public:
|
||||
/* Disable timeout and mark this stream as paused (important to silence spurious onWritable events) */
|
||||
AsyncSocket<SSL>::timeout(0);
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT;
|
||||
|
||||
// forgot about this path, if we sent things off and then ended up pausing!
|
||||
httpResponseData->offset = offset;
|
||||
httpResponseData->outStream = cb;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
+31
-1
@@ -7,13 +7,29 @@
|
||||
|
||||
#include <libusockets.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace uWS {
|
||||
struct Loop {
|
||||
private:
|
||||
static void wakeupCb(us_loop *loop) {
|
||||
std::cout << "wakeupCB called" << std::endl;
|
||||
LoopData *loopData = (LoopData *) us_loop_ext(loop);
|
||||
|
||||
/* Swap current deferQueue */
|
||||
loopData->deferMutex.lock();
|
||||
int oldDeferQueue = loopData->currentDeferQueue;
|
||||
loopData->currentDeferQueue = (loopData->currentDeferQueue + 1) % 2;
|
||||
loopData->deferMutex.unlock();
|
||||
|
||||
/* Drain the queue */
|
||||
for (auto &x : loopData->deferQueues[oldDeferQueue]) {
|
||||
x();
|
||||
}
|
||||
loopData->deferQueues[oldDeferQueue].clear();
|
||||
}
|
||||
|
||||
static void preCb(us_loop *loop) {
|
||||
@@ -63,6 +79,20 @@ public:
|
||||
us_loop_free((us_loop *) this);
|
||||
}
|
||||
|
||||
/* Defer this callback on Loop's thread of execution */
|
||||
void defer(std::function<void()> cb) {
|
||||
LoopData *loopData = (LoopData *) us_loop_ext((us_loop *) this);
|
||||
|
||||
std::cout << "defer called" << std::endl;
|
||||
//if (std::thread::get_id() == ) // todo: add fast path for same thread id
|
||||
loopData->deferMutex.lock();
|
||||
loopData->deferQueues[loopData->currentDeferQueue].emplace_back(cb);
|
||||
loopData->deferMutex.unlock();
|
||||
|
||||
std::cout << "us_wakeup_loop called" << std::endl;
|
||||
us_wakeup_loop((us_loop *) this);
|
||||
}
|
||||
|
||||
/* Actively block and run this loop */
|
||||
void run() {
|
||||
us_loop_run((us_loop *) this);
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
#ifndef LOOPDATA_H
|
||||
#define LOOPDATA_H
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
namespace uWS {
|
||||
|
||||
struct LoopData {
|
||||
friend struct Loop;
|
||||
private:
|
||||
std::mutex deferMutex;
|
||||
int currentDeferQueue = 0;
|
||||
std::vector<std::function<void()>> deferQueues[2];
|
||||
|
||||
public:
|
||||
/* Good 16k for SSL perf. */
|
||||
@@ -14,4 +25,6 @@ public:
|
||||
bool corked = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // LOOPDATA_H
|
||||
|
||||
Reference in New Issue
Block a user