Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gc experiment #14668

Draft
wants to merge 40 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a660692
Rewrite node:http
Jarred-Sumner Oct 6, 2024
deb44aa
okay fastify and express seem to work again
Jarred-Sumner Oct 6, 2024
68ad269
more
Jarred-Sumner Oct 7, 2024
432e92f
Update dev-server.test.ts
Jarred-Sumner Oct 7, 2024
7b295c0
more
Jarred-Sumner Oct 7, 2024
44f1870
tweaks
Jarred-Sumner Oct 7, 2024
4a53dee
Update http.ts
Jarred-Sumner Oct 7, 2024
1f93522
Automatically stop reading body
Jarred-Sumner Oct 8, 2024
d9a1dc7
Fix some of this
Jarred-Sumner Oct 8, 2024
8ca6dba
assign
Jarred-Sumner Oct 8, 2024
3852109
Update server.zig
Jarred-Sumner Oct 8, 2024
2de2cb6
wip
Jarred-Sumner Oct 8, 2024
da3e716
Test
Jarred-Sumner Oct 8, 2024
8212e3a
Merge branch 'main' into jarred/uws
Jarred-Sumner Oct 9, 2024
00e017c
stash this
Jarred-Sumner Oct 9, 2024
9593dcd
Make Next.js 15% faster
Jarred-Sumner Oct 10, 2024
b7e06a9
Add some more node tests
Jarred-Sumner Oct 10, 2024
7afbae8
Merge branch 'main' into jarred/uws
Jarred-Sumner Oct 14, 2024
dae59b6
Fix a couple tests
Jarred-Sumner Oct 14, 2024
da35833
getting closer
Jarred-Sumner Oct 14, 2024
c24ffec
Fake backpressure
Jarred-Sumner Oct 14, 2024
ca1567e
Fix broken test
Jarred-Sumner Oct 14, 2024
240e795
Update BunObject.cpp
Jarred-Sumner Oct 14, 2024
3446bce
Avoid abort when the response is finished
Jarred-Sumner Oct 14, 2024
f4b4c41
Merge branch 'main' into jarred/uws
Jarred-Sumner Oct 15, 2024
81c007c
Update http.ts
Jarred-Sumner Oct 15, 2024
efd0862
Zlib micro optimization
Jarred-Sumner Oct 15, 2024
6a744fc
Update libuwsockets.cpp
Jarred-Sumner Oct 15, 2024
029e5bd
Update event_loop.zig
Jarred-Sumner Oct 15, 2024
70ce875
Revert "Update libuwsockets.cpp"
Jarred-Sumner Oct 15, 2024
dd0d8ac
A few experiments to reduce time spent in spin loops on other threads
Jarred-Sumner Oct 15, 2024
083a79c
auto cork
Jarred-Sumner Oct 15, 2024
d5ecd8b
Update node_zlib_binding.zig
Jarred-Sumner Oct 16, 2024
b4a1aed
Update zlib.ts
Jarred-Sumner Oct 16, 2024
748eb45
estimated size
cirospaciari Oct 18, 2024
88aa427
Merge branch 'jarred/uws' into ciro/h2-visit
cirospaciari Oct 18, 2024
1585266
`bun run zig-format`
cirospaciari Oct 18, 2024
5e3276c
nop
cirospaciari Oct 18, 2024
1e127cf
not used
cirospaciari Oct 18, 2024
61ccc78
`bun run zig-format`
cirospaciari Oct 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"eventemitter3": "^5.0.0",
"execa": "^8.0.1",
"fast-glob": "3.3.1",
"fastify": "^5.0.0",
"fdir": "^6.1.0",
"mitata": "^0.1.6",
"string-width": "7.1.0",
Expand Down
13 changes: 13 additions & 0 deletions bench/snippets/express-hello.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import express from "express";

const app = express();
const port = 3000;

var i = 0;
app.get("/", (req, res) => {
res.send("Hello World!" + i++);
});

app.listen(port, () => {
console.log(`Express app listening at http://localhost:${port}`);
});
20 changes: 20 additions & 0 deletions bench/snippets/fastify.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Fastify from "fastify";

const fastify = Fastify({
logger: false,
});

fastify.get("/", async (request, reply) => {
return { hello: "world" };
});

const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};

start();
12 changes: 11 additions & 1 deletion packages/bun-usockets/src/eventing/epoll_kqueue.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ void us_loop_run(struct us_loop_t *loop) {
}
}

extern int Bun__JSC_onBeforeWait(void*);
extern void Bun__JSC_onAfterWait(void*);

void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout) {
if (loop->num_polls == 0)
Expand All @@ -246,16 +248,24 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
/* Emit pre callback */
us_internal_loop_pre(loop);

int needs_after_wait = 0;
if (loop->data.jsc_vm) {
needs_after_wait = Bun__JSC_onBeforeWait(loop->data.jsc_vm);
}

/* Fetch ready polls */
#ifdef LIBUS_USE_EPOLL

loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, timeout);
#else
do {
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout);
} while (IS_EINTR(loop->num_ready_polls));
#endif

if (needs_after_wait) {
Bun__JSC_onAfterWait(loop->data.jsc_vm);
}

/* Iterate ready polls, dispatching them by type */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
Expand Down
1 change: 1 addition & 0 deletions packages/bun-usockets/src/internal/loop_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct us_internal_loop_data_t {
char parent_tag;
/* We do not care if this flips or not, it doesn't matter */
size_t iteration_nr;
void* jsc_vm;
};

#endif // LOOP_DATA_H
1 change: 1 addition & 0 deletions packages/bun-usockets/src/loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct
loop->data.parent_tag = 0;

loop->data.closed_context_head = 0;
loop->data.jsc_vm = 0;

loop->data.wakeup_async = us_internal_create_async(loop, 1, 0);
us_internal_async_set(loop->data.wakeup_async, (void (*)(struct us_internal_async *)) wakeup_cb);
Expand Down
4 changes: 4 additions & 0 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,10 @@ struct TemplatedApp {
return std::move(*this);
}

void setOnClose(HttpContextData<SSL>::OnSocketClosedCallback onClose) {
httpContext->getSocketContextData()->onSocketClosed = onClose;
}

TemplatedApp &&run() {
uWS::run();
return std::move(*this);
Expand Down
5 changes: 4 additions & 1 deletion packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ struct HttpContext {
if (httpResponseData->onAborted) {
httpResponseData->onAborted((HttpResponse<SSL> *)s, httpResponseData->userData);
}


if (httpResponseData->socketData && httpContextData->onSocketClosed) {
httpContextData->onSocketClosed(httpResponseData->socketData, SSL, s);
}

/* Destruct socket ext */
httpResponseData->~HttpResponseData<SSL>();
Expand Down
5 changes: 5 additions & 0 deletions packages/bun-uws/src/HttpContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ namespace uWS {
template<bool> struct HttpResponse;
struct HttpRequest;


template <bool SSL>
struct alignas(16) HttpContextData {
template <bool> friend struct HttpContext;
template <bool> friend struct HttpResponse;
template <bool> friend struct TemplatedApp;
private:
std::vector<MoveOnlyFunction<void(HttpResponse<SSL> *, int)>> filterHandlers;
using OnSocketClosedCallback = void (*)(void* userData, int is_ssl, struct us_socket_t *rawSocket);

MoveOnlyFunction<void(const char *hostname)> missingServerNameHandler;

Expand All @@ -51,6 +53,9 @@ struct alignas(16) HttpContextData {
bool isParsingHttp = false;
bool rejectUnauthorized = false;

/* Used to simulate Node.js socket events. */
OnSocketClosedCallback onSocketClosed = nullptr;

// TODO: SNI
void clearRoutes() {
this->router = HttpRouter<RouterData>{};
Expand Down
62 changes: 48 additions & 14 deletions packages/bun-uws/src/HttpResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ struct HttpResponse : public AsyncSocket<SSL> {

/* Called only once per request */
void writeMark() {
if (getHttpResponseData()->state & HttpResponseData<SSL>::HTTP_WROTE_DATE_HEADER) {
return;
}
/* Date is always written */
writeHeader("Date", std::string_view(((LoopData *) us_loop_ext(us_socket_context_loop(SSL, (us_socket_context(SSL, (us_socket_t *) this)))))->date, 29));
getHttpResponseData()->state |= HttpResponseData<SSL>::HTTP_WROTE_DATE_HEADER;
}

/* Returns true on success, indicating that it might be feasible to write more data.
Expand Down Expand Up @@ -113,7 +117,8 @@ struct HttpResponse : public AsyncSocket<SSL> {
httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
}

if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) {
/* if write was called and there was previously no Content-Length header set */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED && !(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {

/* We do not have tryWrite-like functionalities, so ignore optional in this path */

Expand Down Expand Up @@ -145,14 +150,16 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
}
}
} else {
this->uncork();
}

/* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */
this->resetTimeout();
return true;
} else {
/* Write content-length on first call */
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) {
if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_END_CALLED))) {
/* Write mark, this propagates to WebSockets too */
writeMark();

Expand All @@ -162,7 +169,8 @@ struct HttpResponse : public AsyncSocket<SSL> {
Super::write("Content-Length: ", 16);
writeUnsigned64(totalSize);
Super::write("\r\n\r\n", 4);
} else {
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER;
} else if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_WRITE_CALLED))) {
Super::write("\r\n", 2);
}

Expand Down Expand Up @@ -207,6 +215,8 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
}
}
} else {
this->uncork();
}
}

Expand Down Expand Up @@ -427,7 +437,7 @@ struct HttpResponse : public AsyncSocket<SSL> {

/* End the response with an optional data chunk. Always starts a timeout. */
void end(std::string_view data = {}, bool closeConnection = false) {
internalEnd(data, data.length(), false, true, closeConnection);
internalEnd(data, data.length(), false, !(this->getHttpResponseData()->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER), closeConnection);
}

/* Try and end the response. Returns [true, true] on success.
Expand All @@ -440,7 +450,7 @@ struct HttpResponse : public AsyncSocket<SSL> {
bool sendTerminatingChunk(bool closeConnection = false) {
writeStatus(HTTP_200_OK);
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_WRITE_CALLED | HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER))) {
/* Write mark on first call to write */
writeMark();

Expand All @@ -455,33 +465,46 @@ struct HttpResponse : public AsyncSocket<SSL> {
}

/* Write parts of the response in chunking fashion. Starts timeout if failed. */
bool write(std::string_view data) {
bool write(std::string_view data, size_t *writtenPtr = nullptr) {
writeStatus(HTTP_200_OK);

/* Do not allow sending 0 chunks, they mark end of response */
if (!data.length()) {
if (writtenPtr) {
*writtenPtr = 0;
}
/* If you called us, then according to you it was fine to call us so it's fine to still call us */
return true;
}

HttpResponseData<SSL> *httpResponseData = getHttpResponseData();

if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();

writeHeader("Transfer-Encoding", "chunked");
writeHeader("Transfer-Encoding", "chunked");
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
}

Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);
} else if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
writeMark();
Super::write("\r\n", 2);
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
}

Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);

auto [written, failed] = Super::write(data.data(), (int) data.length());
/* Reset timeout on each sended chunk */
this->resetTimeout();

if (writtenPtr) {
*writtenPtr = written;
}

/* If we did not fail the write, accept more */
return !failed;
}
Expand Down Expand Up @@ -628,6 +651,17 @@ struct HttpResponse : public AsyncSocket<SSL> {
data->received_bytes_per_timeout = 0;
}

void* getSocketData() {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();

return httpResponseData->socketData;
}

void setSocketData(void* socketData) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();

httpResponseData->socketData = socketData;
}

void setWriteOffset(uint64_t offset) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
Expand Down
5 changes: 4 additions & 1 deletion packages/bun-uws/src/HttpResponseData.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,14 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
HTTP_WRITE_CALLED = 2, // used
HTTP_END_CALLED = 4, // used
HTTP_RESPONSE_PENDING = 8, // used
HTTP_CONNECTION_CLOSE = 16 // used
HTTP_CONNECTION_CLOSE = 16, // used
HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, // used
HTTP_WROTE_DATE_HEADER = 64, // used
};

/* Shared context pointer */
void* userData = nullptr;
void* socketData = nullptr;

/* Per socket event handlers */
OnWritableCallback onWritable = nullptr;
Expand Down
14 changes: 14 additions & 0 deletions packages/bun-uws/src/Loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "LoopData.h"
#include <libusockets.h>
#include <iostream>
#include "AsyncSocket.h"

extern "C" int bun_is_exiting();

Expand Down Expand Up @@ -52,6 +53,15 @@ struct Loop {
for (auto &p : loopData->preHandlers) {
p.second((Loop *) loop);
}

void *corkedSocket = loopData->getCorkedSocket();
if (corkedSocket) {
if (loopData->isCorkedSSL()) {
((uWS::AsyncSocket<true> *) corkedSocket)->uncork();
} else {
((uWS::AsyncSocket<false> *) corkedSocket)->uncork();
}
}
}

static void postCb(us_loop_t *loop) {
Expand Down Expand Up @@ -148,6 +158,10 @@ struct Loop {
getLazyLoop().loop = nullptr;
}

static LoopData* data(struct us_loop_t *loop) {
return (LoopData *) us_loop_ext(loop);
}

void addPostHandler(void *key, MoveOnlyFunction<void(Loop *)> &&handler) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);

Expand Down
1 change: 1 addition & 0 deletions packages/bun-uws/src/LoopData.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ struct alignas(16) LoopData {
}
delete [] corkBuffer;
}

void* getCorkedSocket() {
return this->corkedSocket;
}
Expand Down
Loading