Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Tokamak – Server-side framework for Zig (github.com/cztomsik)
109 points by cztomsik 10 months ago | hide | past | favorite | 26 comments



As a Zig developer with one of the more popular HTTP server libraries around (http.zig), this is impressive and good use of comptime and (good) abuse of Zig's anytype. I'm looking forward to learning from the codebase.

But, it's very slow. A quick test shows Sinatra is about 2x faster. Maybe I'm in the minority, but I feel that a primary reason to give up a GC is for performance.


This is really weird; it's not even obvious how to spend so much time in this code. By any chance, have you tried profiling? Just really curious.


Like many http server implementations in Zig, this is a wrapper around `std.http.Server`. `std.http.Server` only exists as a mechanism to test the `std.http.Client` (which is needed for things like, downloading packages). So `std.http.Server` isn't built to be fast, secure or robust.

I believe you _can_ get keepalive working with `std.http.Server`, but it seems like Tokamak isn't using it that way. The implementation is a thread-per-connection. Those are the two most obvious issues specific to this implementation.

But I believe the bulk of the issues relate to `std.http.Server`. It shouldn't be public/used.


Oh, I didn't know std.http.Server is not supposed to be used. I guess I could just use your project (as dependency) and that would fix that perf issue? And do a thread-pool and keep-alive, of course.

TBH perf was not my priority yet, I only wanted predictable memory usage and Zig was perfect choice for that (when compared to node.js).


The biggest issue with std.http.Server is [1]:

   This server assumes *all* clients are well behaved and standard compliant; it can and will deadlock if a client holds a connection open without sending a request.
Atop your readme, you point out that nginx or another reverse proxy should be used. Kudos for that.

As for performance, I'd be curious what gains you get using `std.http.Server` with keepalive and a threadpool. Possibly you can re-use your ThreadContext - having 1 per thread in the threadpool that you can re-using. `std.Thread.Pool` is also very poorly tuned for a large number of small batch jobs, but that's a place to start.

[1] https://github.com/ziglang/zig/blob/b3aed4e2c8b4d48b8b12f606...


Hm, thanks for pointing that out. I need nginx for SSL anyway, so I was not even thinking about going raw :)

I'm wondering where exactly is the problem in the std.http.Server because it might be easy to fix. Maybe it's just that nobody cared yet... But 2x slower than Ruby sounds awful.

> Possibly you can re-use your ThreadContext

Yes, this is the idea, and extending the injector for request-scoped and thread-scoped dependencies.


Yeah, I'm with you, I thought this thing was just waiting for some "function colouring" behaviors to be ironed out in zig before it was to be polished. I guess comments from https://news.ycombinator.com/item?id=35991684 made me think this but it's good to have been informed that this in fact is not the case (and is closer to a com.sun.net.httpserver.HttpServer as a ref for us ole java folk)


> A quick test shows Sinatra is about 2x faster.

There must be something very wrong


Putting the HTTP method and path directly in the function's name using Zig @-syntax is something I've never seen before. Clever.


the name tokamak - funny choice! Will barely achieve fusion with an huge and complex implementation. Surely not the intended implicature.


I'm not a Zig programmer, but this framework seems pretty nice! Honestly, a lot nicer than I would've expected from a language that explicitly targets lower-level dev[1].

[1]: Rust also has a focus on low-level programming, but has many higher level constructs / a more complicated type system so I'm not too surprised when I see nice web frameworks in that ecosystem


I think there are multiple reasons why Zig is great fit for this:

- zig has powerful meta-programming, it's a bit like functional programming with structs. I would consider that high-level concept, at least comparable to traits and constraints

- zig has anytype duck-typing. in any function, you can say that some arg is anytype, and then you can pass anything to it. type-checking still works, because it's done when you actually instantiate the function with known type. this is great for flat and simple API surface.

- server-side code usually have short life-time (request), so you can just use arena for all allocations, and free everything when you finish, this is super-simple to do in zig, because every api accepts allocator if it needs to allocate. rust arenas have lifetimes, which makes them problematic to use/embed deep down in the hiearchy. zig is not aiming for 100% safety so this is easy.


> - zig has anytype duck-typing. in any function, you can say that some arg is anytype, and then you can pass anything to it. type-checking still works, because it's done when you actually instantiate the function with known type. this is great for flat and simple API surface.

Is this any different from generics? Rust lets you define arguments with `impl Trait` as a shorthand for a generic type `T: Trait`, but this sounds pretty similar to just defining a function over a generic type `T`, albeit with different syntax.


Yes, fundamentally. In Rust if you take a parameter of generic type T without any bounds, you cannot call anything on it except for things which are defined for all types. If you specify bounds, only things required by the bounds can be called (+ the ones for all types). Another difference is where you get an error when you try pass something which doesn't adhere to a certain trait. In Rust you will get an error at the call site, not at the place of use (except if you don't specify any bounds).

Zig is doing just fine without any trait mechanism and it simplifies the language a lot but it does come up from time to time. The usual solution is to just get type information via @typeInfo and error out if the type is something you're not expecting [0]. Not everybody is happy about it though [1] because, among other things, it makes it more difficult to discover what the required type actually is.

[0] https://github.com/ziglang/zig/blob/b3aed4e2c8b4d48b8b12f606...

[1] https://github.com/ziglang/zig/issues/17198


Ah okay, so this is like C++ templates then. This always feels a bit like halfway to duck typing to me; it'll still get caught at compile time, but I'll get errors at every single call site where I pass something wrong rather than just one in the definition, like you mentioned. I have the same gripes with Go's interfaces, although I think I'd prefer Zig's way of doing it because the experience would essentially be the same, just with less boilerplate.


> Is this any different from generics?

Depends on the way generics is implemented in the language you're talking about.

In D, for example, Zig's `anytype` is equivalent to using a template type `T` in D without any constraints. The result is the same: the implementation can call any method, but it must exist when the type is instantiated (on an invocation).

Example in D:

https://run.dlang.io/?compiler=dmd&args=-unittest&source=str...


same thing, done in a different way. the only notable difference (except of writing less code) is that zig does not type-check the code which you don't use, so the genericity goes a bit further than what you can do in rust.


I love the use of `@hasDecl(root, "mime_types")`. I didn't realize that `@import("root")` was a thing.

https://github.com/cztomsik/tokamak/blob/main/src/mime.zig


Interesting. What mechanism does Zig use to hook up function argument dynamically depending on what is declared in the function (or am I reading that wrong)? Not sure I've ever seen something quite like it.



Looks great! Also TIL about AVA https://github.com/cztomsik/ava


thanks :) yeah, ava is my pet project, but there was very little activity lately because I was tight on deadline with another project. I hope to get back to it around next week.


I love it, especially the Quick Tools tab. Powerful yet easy to use. That's the best LLM UI I've seen so far, and it immediately replaced Private LLM (even though I paid for it).


Thank you for your kind words :) I have so many ideas how to make it even better but the time has not been my friend lately :-D



Why would it? That name is also derived.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: