[HN Gopher] Writing Rust the Elixir Way ___________________________________________________________________ Writing Rust the Elixir Way Author : bkolobara Score : 216 points Date : 2020-11-30 12:29 UTC (10 hours ago) (HTM) web link (kutt.it) (TXT) w3m dump (kutt.it) | FpUser wrote: | >"Even though the threads don't do anything, just running this on | my MacBook forces it to reboot after a few seconds. This makes it | impractical to have massive concurrency with threads." | | That is a problem of your MacBook. I can run thousands of threads | when testing some servers sustainably and with no problems on my | Windows and Linux laptop, never mind desktop and real servers. So | it is pretty much practical. Whether it makes sense pretty much | depends on what are you doing in particular. Your conclusion | means zilch without context. | lalaithion wrote: | Running this test on my Linux desktop takes 9.9 GB of virtual | memory (although very little is actually resident) and 42% of | my CPU. So, it doesn't crash my computer, but this seems like | it's way too much resource usage. | FpUser wrote: | Reserved stack space that can be adjusted | anonymousDan wrote: | Can your runtime handle non rust wasm code? | bkolobara wrote: | Yes! I only provide a higher-level library for Rust, but you | can use the lower-level host functions from any WASM code if | you run it on Lunatic. | bausano_michael wrote: | The submitted link does no longer for me, here's a link to the | original article: https://dev.to/bkolobara/writing-rust-the- | elixir-way-2lm8 | akhilcacharya wrote: | To me, this looks _extremely_ promising from a performance and | developer ergonomics point of view and a fantastic use case for | WASM and WASI - only limiting factor seems to be the rollout for | WASI networking support. | toast0 wrote: | > Now you can write regular blocking code, but the executor will | take care of moving your process off the execution thread if you | are waiting, so you never block a thread. | | This is super important and awesome. It's a big part of what | makes Erlang simpler to write code for than other highly | concurrent environments. | anthony_doan wrote: | Yeah it because the Erlang VM have a preemptive scheduler and | each process have a mailbox which gets a designated amount of | time to access cpu. | | You can do a for(1) for loop and it won't hog the cpu because | it get premptive. The problem is when you have large amount of | problematic mails in the mailbox.... | lostcolony wrote: | So having written production Erlang, I have literally never | had an issue with large amounts of problematic mails in the | mailbox. It's like, yeah, technically it's possible, since | there's no forced constraints around what kinds of messages a | given process can be sent (which is intentional; after all, | the process may enter a later receive statement that knows | how to handle those kinds of messages), but it's really easy | to do correctly. And you can always program defensively if | you want to limit the kinds of messages you respond to by | having a wildcard pattern match at the end of a receive. | toast0 wrote: | If you're not running into overfull mailboxes, you're not | trying hard enough! :D | | Usually, it's some process that's gotten itself into a bad | state and isn't making any progress (which happens, we're | human), so ideally you had a watchdog on it it, to kill it | if it makes no progress, or flag for human intervention, or | stop sending it work, cause it's not doing any of it. | | But sometimes, you get into a bad place where you've got | much more work coming in than your process can handle, and | it just grows and grows. This can get worse if some of the | the processing involves send + receive to another process | and the pattern doesn't trigger the selective receive | optimization where the current mailbox is marked at | make_ref() and receive only checks from the marker, instead | of the whole thing. | | If you miss that optimization, the receives will check the | whole mailbox, which takes a long time if the mailbox is | large, and tends to make you get further behind, making it | take longer, etc, until you lose all hope of catching up, | and eventually run out of memory; possibly triggering | process death if you've set max_heap_size on the process | (although where I was using Erlang we didn't set that), or | triggering OS process death of BEAM when it can't get more | ram because allocation failed or OOM killer, or triggering | OS death, if it has trouble when BEAM sucks in all the | memory and can't muster its OOM killer in time and just | gets stuck or OOM kills the wrong thing. | filmor wrote: | Overfull mailboxes can be problematic even with a wildcard | `receive` and can happen if some branch of the control flow | is slower than expected. We have some places in our code | that essentially do manual mailbox handling by merging | similar messages and keeping them in the process state | until it's ready to actually handle them. | ibraheemdev wrote: | async-std has been doing this for a while. The runtime detects | blocking tasks automatically and offloads them to a seperate | thread. | | EDIT: The automatic blocking detection of async-std was | abandoned after further discussion | bkolobara wrote: | There is an important difference here. In Lunatic all | syscalls are non-blocking, they are not just offloaded to a | separate thread. However, they look like regular "blocking" | functions from the developer's perspective. Under the hood | Lunatic uses async-wormhole[0] to make this work. | | [0]: https://github.com/bkolobara/async-wormhole | mwcampbell wrote: | Did that actually go into a release? The last I heard was | that they tried it on a branch about a year ago and then | abandoned it. | ibraheemdev wrote: | I though it had been merged but it looks like they remove | the auto blocking detection and merge a revised version of | the runtime [1]. | | 1: https://github.com/async-rs/async-std/pull/733 | IshKebab wrote: | > but I will call them processes to stay close to Elixir's naming | convention. | | That seems like an extremely confusing mistake given that there | is already a very closely related concept called a "process"? | | I would consider calling them "workers" or "isolates" since | unless I'm mistaken, you have basically recreated WebWorkers (AKA | isolates in V8/Dart)? | | Presumably this also means you can't share memory? Very neat idea | anyway! | stepbeek wrote: | Is this the perfect Hacker News title? | bkolobara wrote: | Author here! I will take some time to answer any questions. | faitswulff wrote: | So must the `crashing_c_function()` in your example be compiled | to WASM before it can be used in Lunatic? Another comment | elsewhere asked: | | > Well then that is not comparable to NIFs at all. In fact it | is an extremely misleading example...A lot of the time you can | not compile the C code to wasm and what do you do then? How do | you interface with OS libraries? | | https://news.ycombinator.com/item?id=25255955 | bkolobara wrote: | Yes, it must be compiled to Wasm to work. | qw3rty01 wrote: | Is there a reason you used threads in the rust example instead | of tasks? I think it would have been more useful to compare | against proper rust async concurrency: I ran 200k sleep tasks | over 12 threads using `async-std` and each thread only used up | ~30% cpu (~60% on `tokio`), and <10% with 20k tasks. | | What is the proper way to call `Process::sleep` in your | example? I don't see it in the lunatic crate or documentation, | and I can't compare results without running the same test in | lunatic. | | Edit: I guess async rust is mentioned, but it doesn't really | explain in detail what lunatic is solving that async rust | doesn't provide, besides a vague "more simple" and "extra | features," which the example doesn't really show. | jimbokun wrote: | What do you see as the advantage of using these Rust + WASM | running on Lunatic processes, versus developing in Elixir? | bkolobara wrote: | Using C bindings without fear would be a big one. If you use | NIFs in Elixir you may bring down the whole VM with a | segmentation fault. While Lunatic limits this failure to only | the affected process. WASM was designed to be run inside the | browser so the sandboxing capabilities are much stronger. | | Another would be raw compute power, WASM comes close to | native in many cases. | nahuel0x wrote: | Other advantages over BEAM: Static typing, multiple | languages with a WASM target available, live migration of | process to the browser. | dwoot wrote: | Recently started my journey with Rust having worked with Elixir | in production for about two years, but I keep an ear out on | Rust and Elixir/Erlang development | | My question: are you familiar with the Lumen project? | https://github.com/lumen/lumen#about. Both projects appear to | have some overlap. Secondly, what, if any, will be the primary | differentiators here? (I've not looked at either projects in | too much detail) | | One of its creators, Paul Schoenfelder (Bitwalker), you're | probably familiar with as he's authored a few popular libraries | in Elixir and the other core developer, Luke Imhoff, is | ensuring that WASM is taking players like Elixir/Erlang into | account being a part of one of the organizations or committees | if I recall correctly | caust1c wrote: | Coming from Go which introduced me to the concept of | lightweight threads, I really miss goroutines and Go's | concurrency model with channels/select/cancellation/scheduling. | | However, targeting WASM seems like a pretty huge compromise to | doing this natively in Rust. Conversely, it looks like what | you're trying to do wouldn't currently be possible without the | WASM/WASI stack. Curious what your thoughts are about not | implementing it as a Rust runtime target? Did you investigate | this and why did you rule it out if so? | latch wrote: | Go's concurrency model seems like a compromise: | | https://play.golang.org/p/DAoobz43yud | | OR | | https://play.golang.org/p/m0VntGBORBw | | Also, I can't think of what scheduling capabilities Go's | concurrency has? | nkozyra wrote: | I'm not sure I understand your second example qualm. That's | logical described-on-the-box behavior. | bkolobara wrote: | I don't see it as a compromise. Using Wasm allows for some | really nice properties, like compiling once and running on | any operating system and CPU architecture. Lunatic JIT | compiles this code either way to machine code so the | performance hit should not be too big. | | When you say "implementing it as a Rust runtime target", I | assume you mean targeting x86/64. In this case it would be | impossible to provide fault tolerance. The sandboxing | capability of Wasm is necessary to separate one processes | memory from another. | nahuel0x wrote: | Note, the Go model falls short against the Erlang/Lunatic | one. You can't externally kill a goroutine at arbitrary | points (you need to cancel them with a cancel chan / context | with manual checking). You can't prioritize a goroutine over | others. You can't serialize a goroutine state and send it | over the network. You can't fork a goroutine. You can't | sandbox a goroutine. Etc. | | Using WASM is a tradeoff, but WASM is very fast, surely | faster than BEAM process. | didibus wrote: | > You can't externally kill a goroutine at arbitrary points | | Why not? Isn't that just an artifact of Go's | implementation, or is there fundamentally something in CSP | that prevents this? | skybrian wrote: | It's essentially cultural. A fundamental assumption of | the Go language, implementation, libraries, and community | is that goroutines share memory. You don't write | concurrent Go code assuming that a Goroutine could | arbitrarily disappear, so breaking this assumption will | break things. | | Lunatic is not like that: | | > Each Lunatic process gets their own heap, stack and | syscalls. | | Similarly, Rust code that uses threads and/or async isn't | going to work in Lunatic (or at least won't get the | advantages of its concurrency model) without being | rewritten using Lunatic's concurrency primitives. The | concurrency model is more like JavaScript workers or Dart | isolates, though hopefully lighter weight. | | I'm guessing that the Rust _language_ might work well | because move semantics assumes that values that aren 't | pinned can be moved, and that's a better fit for | transmitting messages between processes. But there will | probably be a lot of crates that you can't use with | Lunatic. If it became popular, it would be forking the | community at a pretty low level. You'd have some crates | that only work with Lunatic and others that use async or | threads. | nahuel0x wrote: | I think the Lunatic architecture can enable shared memory | regions between processes and between processes and host in | addition to the erlang-like shared nothing approach. That would | be an advantage over (non-NIF) erlang for some use-cases. Do | you plan something like that? Can you easily map a data | structure in Rust? (I think is doable between WASM process but | not sure about between WASM and native host). | | Another question: What about sending code between process? Like | sending a fun between Erlang process. | | IMHO this architecture has the potential to go beyond BEAM, | good work! | bkolobara wrote: | Thank you! This is something I want to support and there is | some WIP code for it. Currently I'm only waiting on Wasmtime | (the WASM runtime used by Lunatic) to get support for shared | memory. | | Regarding the question about sending code, this also can be | implemented. Wasm allows you to share function indexes | (pointers) and dynamically call them. | asimpletune wrote: | One question that I have is at what point do you call something | a VM? | | Also, I haven't looked at your code, so maybe this is not | understanding things correctly, but wrt targeting wasm, could | your work ever make it into the actual rust runtime? | steveklabnik wrote: | > into the actual rust runtime? | | Rust has the same level of runtime as C, with no plans to | expand it. So, regardless of how awesome this project is, | this is unlikely. | | (Notably, there is no "Rust runtime" for async, you have to | bring your own if you want to do it.) | toast0 wrote: | This is a bit out of left field, and perhaps more to motivate | others to try, but... | | Have you considered making this a port driver for BEAM? Then | you could call some function from Elixir to launch a wasm actor | (that happens to be written in rust)? | | Your BEAM would still be imperiled if the Lunatic? layer | violates conventions, of course; but it may (or may not) be | simpler than reinventing the rest of OTP? | ibraheemdev wrote: | > working with async Rust is not as simple as writing regular | Rust code | | Working with async Rust is very simple if you are not writing | futures yourself. Tokio and async-std provide api's that mirror | std except for the fact that you have to add `.await` to the | end of the operation and add the `async` keyword to your | function. With proc-macros, `async main` is just a matter of | adding a small annotation to the top of the function. Async | functions in traits are blocked due to compiler limitations, | but `async-trait` makes it easy. What part of async Rust is | more complicated than synchronous Rust? | | > and it just doesn't provide you the same features as Elixir | Processes do. | | Can you explain this? How is tokio's `task` different then | elixir processes? From the tokio docs: | | > Another name for this general pattern is green threads. If | you are familiar with Go's goroutines, Kotlin's coroutines, or | Erlang's processes, you can think of Tokio's tasks as something | similar. | _flux wrote: | And what do you do with functions from external crates that | take callback functions but are not async themselves? | | You are now limited to non-async functions and if the | operation of the crate depends on the return values of those | functions, you will need some extreme measures to make it | work. | akiselev wrote: | _> What part of async Rust is more complicated than | synchronous Rust?_ | | The Rust part, of course :) Seriously though, the compiler | error messages alone make it a major pain - although I can't | figure out if it's an issue of maturity (language and | ecosystem), a fundamental tradeoff with Rust's borrow | checker, or me just getting way ahead of myself. | | I can rarely go a few days of async programming in Rust | before running into some esoteric type or borrow checker | issue that takes hours to solve because I borrowed some non | Send/Sync value across an await point and the compiler | decides to dump a hot mess in my lap with multi-page long | type signatures (R2D2/Diesel, looking at you). | estebank wrote: | Those subpar diagnostics are _a bug_. The underlying | reasons are that | | - The async/await feature _is an MVP_ | | - async/await desugars to code you can write yourself that | leverages a bunch of relatively advanced features, namely | trait bounds and associated types | | - It also leverages a nightly only feature (generators) | | - Async programming is inherently harder :) | | - We've had less time to see how people _misuse_ the | features to see _where_ they land in order to clean up | those cases and make the errors as user friendly as they | can be | | Put all of those together and the experience of writing | async code in Rust is similar to the experience of writing | regular Rust code maybe two or three years ago. When you | encounter things like this, please file a ticket at | https://github.com/rust-lang/rust/issues so that we can fix | them. | masklinn wrote: | > Working with async Rust is very simple if you are not | writing futures yourself. | | > Tokio and async-std provide api's that mirror std except | for the fact that you have to add `.await` to the end of the | operation and add the `async` keyword to your function. | | There are absolute pain in the ass problems with `async` | currently, in large part due to async closures not being a | thing, which means it's very hard to create anything other | than an `FnOnce` unless you desugar the future entirely. | ibraheemdev wrote: | Async `Fn*` traits are possible on nightly with the | `unboxed_closures` and `async_closure` features. | estebank wrote: | I don't consider things enabled by nightly features to be | "possible" today, only "potentially possible at some time | in the future". The days of using nightly because of a | single needed feature in a popular crate are (in my eyes) | gone. | masklinn wrote: | Yes, by "not being a thing" I meant "not being stable", I | thought I'd edited before posting but apparently I only | thought of doing so, sorry 'bout that. | romanoderoma wrote: | Erlang processes are not green threads. | | Green threads can share memory while Erlang processes cannot, | they are strictly forbidden to do it. | | Also Erlang scheduler is highly optimized to work as a soft | real-time platform, so they never run for infinite amount of | time, they never block and never (at least that's the goal) | bring down the entire application, the worst thing that can | happen is that everything slows down but it's still | functional and responsive. | | I don't know about Tokio. | ibraheemdev wrote: | > Erlang processes are not green threads. Green threads can | share memory while Erlang processes cannot, they are | strictly forbidden to do it. | | So message passing is the only way to communicate between | proccesses? I guess that makes sense with elixir being a fp | language. This was not clear in the article: | | > Lunatic takes the same approach as Go, Erlang and the | earlier implementation of Rust based on green threads. | svrtknst wrote: | Basically only message passing. As another poited out, | you can use FFI calls and Erlang Term Storage, possibly | some other means to communicate, but the central feature | is that each process has an ID, and then you send() a | message to it. | | each process also has a receive() block where you | essentially listen to everything that ends up in your | mailbox and pattern match on it to take action. | dnautics wrote: | > So message passing is the only way to communicate | between proccesses? | | There are escape hatches. Obviously, you can do something | really heavy like open a network port or open a file | descriptor, but it also provides you with use ets, which | is basically "really fast redis for intra-vm stuff" and | you can transact over shared memory if you drop down to | FFI. | sa1 wrote: | In general thinking about concurrent code in terms of threads | is easier than thinking in terms of async code(it's a lower | level abstraction). | | > Can you explain this? How is tokio's task different then | elixir processes? | | Tokio's tasks and go's goroutines and kotlin's coroutines are | cooperatively scheduled, i.e a infinite loop can block other | tasks from running. | | Erlang and lunatic have pre-emptive schedulers(similar to a | OS scheduler) that schedule processes fairly by giving time | slices to threads. | ibraheemdev wrote: | I'm not familiar with erlang/elixir so I assumed that | processes were similar to goroutines: | | > In other languages they are also known as green threads | and goroutines, but I will call them processes to stay | close to Elixir's naming convention. | dnautics wrote: | they're similar, but the developer ergonomics around | processes are way better. It's difficult to mess up, | coding in the BEAM feels like bowling with those rubber | bumpers in the gutter lanes, especially around very | difficult concepts like concurrency and failure domains. | | Go makes it easy to mess up because the abstractions are | superficially simple, but it's a pretty thin layer that | you punch through. | jolux wrote: | The BEAM scheduler is not quite preemptive as I understand | it, but in practice it gets close because instead of yield | points being defined manually with "await" they are | inserted automatically when you return from a function or | call a function or do some number of other fundamental | operations, and the pure functional nature of the language | means that functions are short and consist almost solely of | other function calls. | k__ wrote: | lol, so BEAM is for schduling like Rust is for memory | allocation? | sa1 wrote: | That is how haskell's scheduler works, but I was not | aware that it was the same with BEAM. Makes sense. | jolux wrote: | https://blog.stenmans.org/theBeamBook/#_reductions | dmm wrote: | A favorite HN comment that discusses this: | https://news.ycombinator.com/item?id=13503951 | | Go read it all but here's a relevant quote: | | """ So how does Erlang ensure that processes don't hog a | core forever, given that you could theoretically just | write a loop that spins forever? Well, in Erlang, you | can't write a loop. Instead of loops, you have tail-calls | with explicit accumulators, ala Lisp. Not because they | make Erlang a better language to write in. Not at all. | Instead, because they allow for the | operational/architectural decision of reduction- | scheduling. """ | jolux wrote: | That is a fantastic comment. I also recommend the BEAM | book to those who want to go deeper: | https://blog.stenmans.org/theBeamBook/ | | I haven't finished it yet but the chapters on scheduling | are great. | dmitriid wrote: | There's also BEAM Wisdoms: http://beam- | wisdoms.clau.se/en/latest/ | codetrotter wrote: | Lunatic seems very enticing to me. I remembered it, but not its | name, when I read the intro to this blog post and my question | was going to be if you'd tried it and what you thought of it | but then a couple of paragraphs further into the blog post I | learned that you are the author of Lunatic :P | | But instead I would like to ask about the future of Lunatic. | What is you vision for it? Like, is it a hobby project that you | are doing for fun or is it an endeavor that you intend for to | be powering big systems in production? | | Furthermore, how will you fund the development of Lunatic? And | where will other contributors come from? Will they be people | scratching their own itches only or will you hire people to | work on it with a common roadmap? | bkolobara wrote: | I started working on Lunatic around a year ago in my free | time. Until now I just wanted to see if it was even | technically possible to build such a system. Now that I have | the foundation in place and you can build some "demo" apps, | like a a tcp server, I'm starting to explore options for | funding. From this point on I think that the progress is | going to be much faster, especially if others find it useful | enough to contribute to it. | scns wrote: | Maybe you can apply at https://prototypefund.de/en/ | mwcampbell wrote: | Here's a possible funding idea: See if you can get one of | the big cloud providers or CDNs to notice the project, so | they can hire you to build something that goes head to head | with Cloudflare Workers. | daxfohl wrote: | We're hiring.... | https://careers.microsoft.com/us/en/job/915502/Senior- | Softwa... | | And definitely looking at doing something like this next | year. | ema wrote: | I'm extremely surprised that creating 2k threads makes mac os | reboot. Sure that's a lot for one application but not a totally | crazy amount. | JoeAltmaier wrote: | It's probably about system resources - VM reservations for each | stack and heap etc. Not a lot of checks inside kernel thread | creation code; and not a lot to do about it if anything fails. | My friend Mike Rowe said it this way: its like having an | altimeter on your car so if you go off a cliff, you know how | far it is to the ground. When hard limits on system resources | are exhausted, it can be very hard to write code to recover. | Imagine if the kernel call fail recovery code needed to create | a thread to handle the exception! | toast0 wrote: | I'm not too surprised. mac os isn't tuned out of the box for | high loads, and in some areas really can't be (there's no | synflood protection, since they forked FreeBSD tcp from months | before that was added, for example) | | Not that 2k threads is really that high; but it's probably high | enough to break something. | DerDangDerDang wrote: | You'd expect it to kill the offending user space process | rather than the OS though, right? | toast0 wrote: | If you hit an explicit limit, I'd expect the thread spawn | to fail, and most processes to panic if thread spawning | failed, sure. | | But if you run below the explicit limit, but above the | implicit limit of whatever continues to work, it's not | surprising to me that the OS just freaked out. | | You could report it to Apple, but their reporting process | is pretty opaque, especially if you're not a registered | developer (because why would you be, if you're just using | mac os because you like the desktop environment, or | whatever). Who knows if they'll fix it, but it's not worth | making a big deal over, because you weren't _really_ | wanting to run 2k threads on mac os anyway, because it | would suck, even if it did work. | | From the other message on the thread; it looks like too | many threads is causing a watchdog timer to fail, leading | to the panic. | DerDangDerDang wrote: | Sure, it just seems odd that the OS lets itself get into | a situation where it can't recover - even in a corner | case like this. | | Like, can I just start 20k threads in WebAssembly and | reboot anyone who visits my site? | | Obviously I'd expect the browser to guard against that... | but I expected the OS to as well, so my expectations may | be way off! | bkolobara wrote: | I believe that this is related to the operating system being | overwhelmed with waking up every 100ms on 2k threads. This | example is not that great though. Depending on the OS and CPU | you should be able to run much higher amount of threads. | Twirrim wrote: | A quick stab on my linux laptop has it hitting at most 25% CPU | utilisation, and consuming almost zero memory. Seems really odd | that this would nuke a Mac somehow. | OskarS wrote: | Yeah, me too, I found that pretty shocking actually. So | shocking that I basically didn't believe it, so I tried myself | (in C++, but it doesn't really make a difference). With 2000 | threads, the computer had no problem whatsoever, the process | only took around 16 megabytes of memory and not very much CPU. | | So I bumped it up to 20,000, thinking that would probably work | as well: the computer immediately crashed and rebooted. I | didn't even get the "kernel panic" screen, it just died (this | is on a 2018 mac mini with a 3.2 Ghz Core i7). When it turned | back on, this was the first line of the error message: | | Panic(CPU 0, time 1921358616513346): NMIPI for unresponsive | processor: TLB flush timeout, TLB state:0x0 | | Weird. I really thought that this wouldn't be an issue at all. | And: if it was an issue, that the OS would be able to handle it | and kill the process or something, not kernel panic. | DerDangDerDang wrote: | Yeah, that's pretty worrying that the OS just punts. I always | thought limiting the potential damage a user space process | could do was one of the main jobs of an OS. | | If you have any more OSes laying around to run the test on, | I'd be interested to hear how well windows and Linux handle | the same thing. | | Because on the face of it this seems like a serious bug in | the OS. I'm only used to seeing this sort of thing with bad | drivers | rapsey wrote: | Wait why does calling a crashing c function not crash the | process? | nahuel0x wrote: | the C code is compiled to WASM bytecodes, they are interpreted | in a WASM VM. If the C function crash, it does it inside the | VM, so the crash is contained. | akashakya wrote: | I don't know much about WASM, but how does this work with | shared libraries? Is it even possible to call a shared | library without any safety guarantees? | brabel wrote: | WASM runs in a sandbox, it's not possible to call a shared | library directly in the same way as you do in C... all sys | calls are "imported" functions that a host exposes to the | WASM code running... this host has the ability to do | anything around a sys call, which is likely how they manage | to forbid access to network/filesystem for example. | | The host can be the browser when running on the browser, or | it can be one of the WASM runtimes (wasmtime, wasmer, | Lucet) | rapsey wrote: | Well then that is not comparable to NIFs at all. In fact it | is an extremely misleading example. | identity0 wrote: | Agreed. It's not a "foreign" function if everything is in | WASM. All the VM sees is WASM code; it doesn't make a | difference if it was originally written in C or Rust. | kibwen wrote: | "Foreignness" is an incidental property, the actual goal | is interoperation. Being able to interoperate with C code | from within the sandbox is both useful and something that | BEAM doesn't do. In the meantime, there's nothing | preventing anyone from doing regular FFI from within the | part of the Rust program that lives outside of Lunatic, | if for whatever reason the sandbox is insufficient. | kibwen wrote: | Why is it misleading? If BEAM could isolate crashes in | NIFs, it absolutely would. Either way the point is that you | are able to leverage C code (though in Rust's case the | point is just to leverage existing code, since you don't | need to drop into C for performance). | rapsey wrote: | Because you are not actually calling a native compiled C | lib you are calling wasm code. A lot of the time you can | not compile the C code to wasm and what do you do then? | How do you interface with OS libraries? | kibwen wrote: | You may be focusing too closely on the C aspect; the | point of the demonstration in the OP is to show that | Lunatic can gracefully sandbox and interoperate with any | code that can be compiled to WASM, of which C code is | just one example. Ideally you wouldn't need to access any | OS libs from within the sandbox (indeed, much of the | point of the sandbox is to provide alternatives to OS | facilities), and even if you did you could still access | those libs in an un-sandboxed form from within your | ordinary Rust code (and yes, crashing at that point would | take down the process, but it's still strictly better to | have an option to run C code that doesn't take down the | process when crashing). | yetkin wrote: | Hi, can you please explain the Wasm part? Is there any particular | reason to use Wasm? I don't know wasm but does it use a specific | threading/concurrency mechanism in it? | kibwen wrote: | Looking at the Lunatic readme: https://github.com/lunatic- | lang/lunatic#architecture | | _" Lunatic treats WebAssembly instances as actors. Using | WebAssembly in this context allows us to have per actor | sandboxing and removes some of the most common drawbacks in | other actor implementations."_ | yetkin wrote: | And it would be something more lightweight then a | process/thread/green thread I presume? | didibus wrote: | Love Erlang and Elixir and Beams concurrency model is quite | interesting. This Rust variant seems very interesting as well. | But I wanted to discuss this part: | | > In most cases it's enough to use a thread pool, but this | approach fails once the number of concurrent tasks outgrows the | number of threads in the pool | | I think something has been lost from all the dialogue about | concurrency. The only way the number of concurrent task can | outgrow the number of threads is when you're handling IO. What I | see rarely discussed here is forms of non-blocking IO and why | those haven't become the norm. Why are we all trying to work | around models that involve blocking on IO? I feel I almost never | hear about using non-blocking IO as an alternative? | | For all other tasks which involve computation, thread pools are | needed to improve throughput and performance, only threads can be | executed in parallel. Yes you can still multiplex many more small | compute task over many threads, if you wanted a really responsive | but overall slower program, that could be an option, but I think | most people going for a concurrent approach aren't doing so for | that, but really just as a way to leverage their IO cards to the | fullest. | | So my question is, what's wrong with having a thread pool | combined with non-blocking IO ? | hosh wrote: | There were some great articles posted recently about async I/O | (in context of modern storage hardware) recently here: | | https://itnext.io/modern-storage-is-plenty-fast-it-is-the-ap... | | https://thenewstack.io/how-io_uring-and-ebpf-will-revolution... | mwcampbell wrote: | > What I see rarely discussed here is forms of non-blocking IO | and why those haven't become the norm. Why are we all trying to | work around models that involve blocking on IO? I feel I almost | never hear about using non-blocking IO as an alternative? | | Maybe I don't understand what you mean by non-blocking I/O, | because it seems to me that it _is_ the norm in many | environments, for better or worse. Async /await syntax is now | available in C#, JavaScript, Python, Rust, and possibly others. | And before that we had callbacks in JavaScript and in Python | frameworks like Twisted and Tornado. So it seems to me that | non-blocking I/O has been widely used and discussed for quite a | while now. | didibus wrote: | I think I don't often hear it in the context of C# and Rust | maybe, or what I mean is, in the context of a multi-threaded | environment, so in combination with a Thread Pool. | | Say you used the thread pool model. And you receive 100 | requests, and you have a thread pool of 50. Your first 50 | requests go to your 50 threads, and the other 50 get queued | up. After any of the running 50 requests make non-blocking | IO, they release the thread back to the pool, and they yield | themselves into an IO waiting queue. Then the scheduler takes | from either the request queue or the IO waiting queue | prioritizing one over the other as see fit. If any of the | queues are full, then the system blocks and waits, dropping | requests that'd come in during that time. | | Or some similar models. | | Is this just async/await that I described? | DylanDmitri wrote: | At least for C# ASP.NET it's basically what you described. | There's a few optimizations, for example the threadpool has | pretty smart autoscaling to maximize throughput. | mwcampbell wrote: | Yes, this is how async/await typically works in Rust, and | in C# when using a server-side framework like ASP.NET Core. | toast0 wrote: | You can do non-blocking IO (and, of course, that's what's | happening behind the scenes here, and in Erlang). The thing is, | a program written with non-blocking IO has a lot different | structure than with blocking IO. | | It can feel clearer and easier to reason about what happens on | a single connection if the code is like: | | accept, read, process, write and somewhere else handles the | concurrency | | Especially if the one system does a lot of different things, | but they're all related enough to be in one OS process, mashing | together a lot of isolated non-blocking IO can be pretty | tricky, and hard to keep track of. | jowi-dev wrote: | So I am very fascinated by both languages and work in Elixir | professionally, with a pretty minimal knowledge of Rust. I was | just wondering what the motivation for creating this project was | rather than just writing the application in Elixir with NIFs in | Rust whenever higher performance is needed? | bkolobara wrote: | If a NIF fails it takes down the whole Erlang VM, so you loose | all guarantees that make Elixir great once you resort to NIFs. | There are many other gotchas, like if you spend too much time | inside the NIF it will affect the stability of your whole | system. This is eliminated by some design choices taken by | Lunatic. | | But as I mentioned in the blog, if you can use Elixir/Erlang do | it! It's a great language and runtime. | jowi-dev wrote: | Very interesting and good to know! I've been keeping a closer | eye on Rust lately as its beginning to look more and more | like it would be foolish to ignore. Rust and Go have really | made it hard to conceptualize where Elixir fits into the dev | world these days. Go just seems to be a faster Elixir in a | lot of ways. I'll keep an eye on this project and if you need | any help - feel free to tag me on github (same username as | this one) | dnautics wrote: | > Go just seems to be a faster Elixir in a lot of ways | | If you haven't worked with both, you just don't know. The | developer experience in go is nowhere near the developer | experience in Elixir. It's just so much easier to write | scalable code in Elixir than Go. Sure it's not as fast, but | it's not god-awful slow (speedwise, you're doing better | than python django, for example), and for 90% of people | shopping for what elixir and go offer, the network is the | bottleneck. | jowi-dev wrote: | I've only worked in Elixir. I think I just recently | experienced some Go "fomo" because it seems to be more or | less attempting to solve the same problem. I can't quite | justify moving to Go from Elixir + Rust, but I'm always | interested in hearing the community at-large take. Thanks | for the response! | dnautics wrote: | I've done both (Go first, actually), and I hated Go. And | all of my dev friends who are still in goland have | nothing but complaints. Small sample size (n ~ 3), but | still. | sdfin wrote: | I'd find it interesting to know what those complaints are | about. | mwcampbell wrote: | To expand on that, the Go runtime is nowhere near the | BEAM. To see why, check out this rant by a developer who | has done a lot of work with Erlang [1], particularly | starting at "Where it got really ugly for me". | | [1]: https://vagabond.github.io/rants/2014/05/30/a-week- | with-go | rch wrote: | I haven't used it professionally, but it seems like the | Rustler project mitigates some of the problems with NIFs. Is | that an accurate impression? | steveklabnik wrote: | Yes, that is why Discord is doing it: | https://blog.discord.com/using-rust-to-scale-elixir- | for-11-m... ___________________________________________________________________ (page generated 2020-11-30 23:01 UTC)