Sometimes server crawhes with SIGABRT pointer being freed was not allocated or SIGSEGV: Illegal storage access. or hangs and won't respond.
Currently memory between threads could be shared only via pointer, with manual memory management. And there are surprisingly lots of cases where it's needed.
proc PageEl(state: State): string =
fmt"""
<html>
<head>
<link rel="stylesheet" href="{server.asset_path("/style.css")}">
</head>
<body>
{AppEl(state)}
</body>
</html>
"""
Note this line server.asset_path("/style.css") it won't compile because the template going to be rendered in some thread, and it won't be possible to access the server variable. And the server is needed becaue it contains list of paths where assets are located. So instead you have to use something like req.asset_path("/style.css") you copy server config and attach it to the request every time when the new request is created. Waste of resources and unflexible and inconvenient way, what if later you need more data from other config?
It won't work with objects, ifs and other limitations. It's possible to workaround as in example below, but would be nice if fmt would sopported any expression.
proc MessageEl(message: Message): string =
let edit = action("edit", (id: message.id))
let delete = action("delete", (id: message.id))
fmt"""
<div id={message.id} class="message flashable">
<span>{message.text.escape_html}</span>
<button on_click={delete}>Delete</button>
<button on_click={edit}>Edit</button>
</div>
"""
Solution
All this makes using multi-threading hard and inconvenient. Yet, the alternative the async has its own set of problems and I don't want to use it either.
Long therm solution is that eventually Nim would support those features and we would have great parallel capabilities.
Short term solution, I think that NGinx + N Single Threaded Nim Workers could be the path for now, that's the way I'm going to use it. Thankfully Nim consumes little memory and works well in single threaded mode.
P.S.
Also, I think a good option could be to add support for JVM or Net backend, and have robust multi-threading right now. It also could attract some devs into Nim from large JVM/Nim communities.
Some general hints: don't use malloc/free in your hot path. Especially in interrupts. Use a kind of pooling solution instead. And don't mix different concepts together (async and threading for instance) if you don't need to. Most of the single / low parallel degree threaded apps perform better. Keep in mind that your server's resources are limited (net-ports for instance).
While 1) is probably a bug made by the user it shows that threading and sharing state is too hard in Nim currently. In a perfect world the user wouldn't be able to make bugs like that unless they tried really hard, and this is the case for the most part when doing single-threaded Nim. But as soon as you get over to the realm of multi-threading it seems like the Nim approach so far has been "you're on your own" by default. Don't get me wrong, I like that we're _able to go low level in Nim, and for some things it is really necessary, but that it is the default when doing threading isn't great.
The situation is slowly improving, and ARC seems to be very nice for threading. But we really needs some better standard library modules that wraps the low-level concepts of threading in a nice user-friendly package.
2) No way to share state among threads
After reading this point, I couldn't help but point out that communicating across threads over channels is a thing.
On top of that, taking a more data-oriented or functional programming approach will simplify your multi-threaded application immediately. If all your code must be able to access the data of your application at all times, then you avoid this problem from the start of your design.
Regarding point no 2, I agree. It can be done currently by using ptr or by switching gc, but some kind of mutex would be a nice addition to the standard library. Nim already has locks, but that requires passing both the lock and the object as ptr, while ensuring that both the lock and the object are still alive until all processing is completed. There is a bit of plumbing there. Some convenient mutex would be a welcoming addition to the standard library.
I ended up implementing a my own version of C++'s shared_ptr, with a few differences. It supports custom destructor (for cleaning up resources), acts as a read-write mutex, and can be safely moved and copied across threads. If anyone is interested in seeing the code, I will be more than happy to share. Maybe we can peer review the code since I am also new to Nim.
People are asking "how-to" questions around threading and sharing memory relatively often.
I think it's a sign that the manual need some more extensive examples/how-tos/tutorials (or links to external resources).
After reading this point, I couldn't help but point out that communicating across threads over channels is a thing.
Maybe I miss something, but Channels doesn't have a way to receive the message with specific id. Say one Thread holds amount of money. And other thread wants to know that, so it sends message "question_id: 42, tell me amount of money", but there's no way to get the reply like channel.recv(42).
It can be done currently by using ptr or by switching gc
Yes, I tried approach with pointers, it, worked but sometimes I got GC-error crashes. I don't know C to debug those cases, so already was worrying if I should be going that path. Then I discovered that std/sharedtables doesn't support the string key/values, probably because it's too hard to do. And so I decided to stop and wait till those features would be more robust and easier to use for people without the C knowledge.
I ended up implementing a my own version of C++'s shared_ptr, with a few differences. It supports custom destructor (for cleaning up resources), acts as a read-write mutex, and can be safely moved and copied across threads. If anyone is interested in seeing the code, I will be more than happy to share. Maybe we can peer review the code since I am also new to Nim.
@hankas Ah, great minds think alike ;) I've also written my own version of shared_ptr that supports custom destructors. The source is here but is a bit messy, with some leftover debugging instrumentation.
Would be happy to combine efforts and submit for inclusion in the fusion lib.
About being able to send and receive messages with id with channels - actually seems like it's possible full snippet
# Answering Service
let answering_service_id = 1
proc answering_service(): void =
listen((question) => fmt"answer to {question}")
answering_service.run_in_thread(answering_service_id)
# Some Worker
proc some_worker(): void =
for i in 1..5:
echo answering_service_id.call(fmt"question {i}")
# Askind in batch
echo call(@[
(answering_service_id, "question 6"),
(answering_service_id, "question 7")
])
some_worker.run_in_thread(2)
Maybe we can peer review the code since I am also new to Nim.
The source is here but is a bit messy, with some leftover debugging instrumentation.
Oh yes, please. There is also: https://github.com/nim-lang/Nim/pull/17333
Sorry for the late reply.
My version of smart pointers is at https://github.com/henryas/nptr . It probably has some quirks and needs more thorough testing.
Looking forward to collaborate and join effort as well.
It probably has some quirks and needs more thorough testing.
It looks pretty slow. 4 allocations for a single shared pointer? :-)
Sometimes server crashes with SIGABRT pointer being freed was not allocated or SIGSEGV: Illegal storage access. or hangs and won't respond. Not sure how to debug such cases, feels scary
It can be difficult to pinpoint it right away, but as mikra said you can use a debugger. Alternatively, place debug prints in various locations and record a sufficient amount of logs. I've spent a lot of time on HTTP servers and web frameworks with Nim before, so these debugging methods have helped me.
Is there any performance difference between allocating several variables individually vs allocating a tuple containing those variables?
Yes, allocating a tuple is much faster.
Spent couple of days watching latest Web tech, meditating, and finally found solution for Nim Web :).
I use nim for data processing, it works well, and I want to use it for Web-tier too. The problem is that features I need will be available in Nim in 2-5 years. And I need it now. Not just multi-threading but also set of feature rich and robust web-related libraries.
The solution for Nim Web is to climb on the shoulders of giant, use same approach as TypeScript and Kotlin and Elixir. And use Nim with other tech, like Elixir Phoenix. And instantly you have tons of advantages:
The question is - how to get all that and write as little non-Nim code as possible?
I think it could be used as a Service Mesh, where stuff like HTTP / Messaging / DB / Networking / Streaming / Routing / Auth / Fail-tolerance etc handled by distributed Elixir Phoenix Proxy Server.
And Nim works a set of N single threaded processes, guarded and protected from messy outer world by Elixir Phoenix. Nim has low memory consumption so this architecture should be ok. Also, web stuff is mostly standard, and don't need to change from project to project, and it's mostly already done, so it should be quite easy to do without the need to write much code.
And with such approach Nim doesn't need to know anything about HTTP, DB, Streaming, etc. It's just a simple Nim process that exposes and calls some RPC and sends messages.
Going to try this approach and see how it works, I think it should be relatively easy to do, as most of that stuff is already there and you just need to plug Nim into the scheme. I'm going to start by using Elixir Phoenix for Server, and see if some of its code could be delegated to RPC Nim, will see how it goes...