Perhaps I'm misunderstanding you, but Dioxus does not use the canvas when rendering in the browser. It uses the DOM with CSS just like any other JavaScript framework does.
The whole paper is not only full of misunderstandings, it is full of errors and contradictions with the implementations.
- Rust is run in debug mode, by omitting the --release flag. This is a very basic mistake.
- Some implementations is logging to stdout on each message, which will lead to a lot of noise not only due to the overhead of doing so, but also due to lock contention for multi-threaded benchmarks.
- It states that the Go implementation is blocking and single-threaded, while it in fact is non-blocking and multi-threaded (concurrent).
- It implies the Rust implementation is not multi-threaded, while it in fact is because the implementation spawns a thread per connection. On that note, why not use an async websocket library for Rust instead? They're used much more.
- Gives VM-based languages zero time to warm up, giving them very little chance to do one of their jobs; runtime optimizations.
- It is not benchmarking websocket implementations specifically, it is benchmarking websocket implementations, JSON serialization and stdout logging all at once. This adds so much noise to the result that the result should be considered entirely invalid.
> To me, the champion is uWebSocket. That's the entire reason why "Node" wins [...]
A big part of why Node wins is because its implementation is not logging to stdout on each message like the other implementations do. Add a console.log in there and its performance tanks.
I started with the World Editor from Warcraft 3 around that age, creating custom maps, gamemodes and spells/abilities. It has scripting capabilities that is somewhere between GUI code and actual code. There's also a lovely community around the World Editor over on https://hiveworkshop.com. Picking the latest custom spell updated as a showcase, if you expand the "Spoiler: Triggers" you can see how the "code" looks like: https://www.hiveworkshop.com/threads/frost-field-v1-0-1.3556...
There are a lot of custom maps in that game that has later become their own standalone games, the most well-known is probably Dota 2 that is the successor to the DotA custom map in Warcraft 3. I hear the Warcraft 3: Reforged update brough Lua support, but the "old" way of using the GUI or JASS is still supported so there is a natural learning path from the simpler GUI code to more advanced JASS or Lua code.
So far the comments here seem to think they are introducing generic ads in the client, while the ads the article describes is to allow game studios to offer virtual rewards (aka hats) in their games when you livestream your gameplay to other Discord users, and those users (viewers) will then be seeing ads while watching that gameplay. This is somewhat similar to the Twitch Bounty Board for streamers and "drops" for viewers. Discord calls this "Sponsored Quests", and seems very unobtrusive.
I'm not the one who wrote the original comment, so I can't modify it. But one should still commit offsets because it is the happy-path; DB transaction successful? Commit offset. If the latter fails due to e.g application crash and you seek at startup to the partition offset stored in the DB + 1, you get exactly-once semantics. There's some more details, e.g you'd have to do the same during consumer group rebalance, and topic configuration also plays a role, for example if the topic is a compacted topic or not, and if you write tombstones, what its retention policy is.
edit: You added some more to your comment after I posted this one, so I'll try to cover them as well:
> One downside is that you leak internals of other system (partitions).
Yeah, sure.
> The other is that it implies serialised processing - you can't process anything in parallel as you have single index threshold that defines what has been and what has yet not been processed.
It doesn't imply serialised processing. It depends on the use-case, if each record in a topic has to be processed serially, you can't parallelize full-stop; number of partitions equals 1. But if each record can be individually processed you get parallelism equal to the number of partitions the topic has configured.
You also achieve parallelism in the same way if only some records in a topic needs to be processed serially, at which point you can use the same key for the records needing to be serially processed and they will end up in the same partition, for example recording the coordinates of a plane - each plane can be processed in parallel, but an individual plane's coordinates need to be processed serially - just use the planes unique identifier as key and the coordinates for the same plane will be appended to the log of the same partition.
Depending on how you use the database it is. If you write the data as well as the offset to the DB in the same transaction, you can then seek to the offset stored in the DB after application restart and continue from there.
I watched the video and thoroughly enjoyed it, thank you for sharing it! I have a question that is perhaps not entirely related to the video, but it touches the topic of context switches. I've read this post [1] by Chris Hegarty, which explains that when calling the traditionally blocking network I/O APIs in the Java stdlib from a virtual thread, it uses asynchronous/poll-based kernel syscalls (IOCP, kqueue, epoll on Windows, Mac and Linux respectively) which I assume is to avoid blocking the carrier threads. That post was written in 2021, does it still hold true today in Java 21?
Reading that, it also makes me wonder what happens for disk I/O? Many other runtimes, both "green thread" ones like Golang and asynchronous like libuv/tokio, use a blocking thread pool (static or elastic) to offload these kernel syscalls to because, from what I've read, those syscalls are not easily made non-blocking like e.g epoll is. Does Java Virtual Threads do the same, or does disk I/O block the carrier threads? For curiosity, does Java file APIs use io_uring on Linux if it is available? It is a fairly recently added kernel API for achieving truly non-blocking I/O, including disk I/O. It doesn't seem to bring much over epoll in terms of performance, but has been a boon for disk I/O and in general can reduce context switches with the kernel by reducing the amount of syscalls needed.
From my perspective they're not entirely equivalent. The async variant seems to be batching getImages and saveImages, while the sync variant gets and saves each image individually, sequentially.
It's worth mentioning that there are some aspects of the virtual thread implementation that is important to take into consideration before switching out the os-thread-per-task executor for the virtual-thread-per-task executor:
1. Use of synchronized keyword can pin the virtual thread to the carrier thread, resulting in the carrier thread being blocked and unable to drive any other virtual threads. Recommendation is to refactor to use ReentrantLock. This may be solved in the future though.
2. Use of ThreadLocals should be reduced/avoided since they're local to the virtual thread and not the carrier threads, which could result in balooning memory usage with extensive usage from many virtual threads.
The main problem I have with reactive programming in Java, at least with Project Reactor which is the only one I've used is the cryptic stack traces. You can regain some information by enabling runtime instrumentation to be used in development, but has too much of a performance hit to be used in production. It doesn't produce much better stack traces either. Trying to read a profiler flamegraph is nigh impossible for reactive code. For the debugger, you need to manually insert breakpoints in each step of the chain, because otherwise you'll just be looking around the reactive library's internal code.
The other problems I have is that as soon as you need to do anything blocking (e.g talking to SQLite or RocksDB over JNI), the whole thing falls apart in terms of threading. Doing a HTTP request somewhere in a reactive chain that does some blocking operations - well shit now you're blocking the reactor-http-netty-* threads, which are fixed so you're worse off than with thread-per-request at that point. Or even just using Caffeine cache, or any caching library really, but Caffeine is one of few that prevents async stampede - well shit now you're running on ForkJoin pool during cache misses, but "reactive" threads on cache hits. You can see how this quickly becomes a tangled mess of thread-hopping and avoiding blocking event loops, and it's extremely complicated to get this right with how subscribeOn/publishOn works and how the profiler can't tell you which threads runs what code because the stack traces are filled with garbage.
You also opt out of a lot of what Java offers in terms of synchronization. Using the synchronization keyword is a big no-no, the JVM can park the thread while waiting for the lock. Now that thread cannot schedule other tasks just because that one task ran into lock contention, and it's highly likely a fixed pool of threads. You're basically left with CAS, AtomicLong/AtomicBoolean/etc, and that's it. I've even seen BlockHound, project reactor's tool to help you find if you are blocking somewhere, trigger "Blocking call to LockSupport.park()!" during a ConcurrentHashMap lookup. Add to this that most caching libraries are implemented on top of ConcurrentHashMap. Getting async right in reactive code has a huge mental overhead and you need intricate knowledge of the libraries you depend on, the platform you run on and what it is that actually enables async in the kernel, and where it is unavailable. Good example of this; that UUID library you use uses a CSPRNG. Have you set the JVM flag to read it from the non-blocking /dev/urandom rather than the default /dev/random on Linux?
I think reactive programming in Java is decent as long as all you're really doing is accepting/performing HTTP requests and talking to a database that has a reactive client connector. In essence, anything that strictly deals with networking, since that's all that can really be made truly async currently, thanks to epoll/kqueue/iocp on linux/mac/windows respectively. Hopefully io_uring will broaden that to file IO as well eventually, but unfortunately that's linux-only for now.
Contrast this with Go's scheduler that just takes care of this for you - no need to think about it. Or Rust + Tokio, where it is explicit with tokio::spawn_blocking and offering async counterparts to std Mutex, RWLock, Channels, etc that doesn't block the current thread on lock contention.
Seems to me like their Channel Servers pretty much does the work of what Cassandra would do, with Consistent Hashing and their Consistent Hash Ring Managers, although it's not detailed how the Channel Servers persist their data and if like Cassandra, they have replication to N Channel Servers. Looks like a specialized variant of how Cassandra works for their use-case, possibly without all the drawbacks of the underlying data structure Cassandra uses for persistence (SSTables), like needing exact ordering of clustering keys when performing queries or dealing with tombstones.
I could see these Channel Servers using something like SQLite for persistence, which would allow the full suite of SQL features without any of the drawbacks of CQL, of which there are many.
The blog post felt a bit high-level and lacking some technical/architectural details that would've given more insight, but nonetheless it was an interesting read!
I'm in the same boat as you, also used Mullvad port forwarding for this because all I can get where I live is mobile 4G internet which is 1) behind NAT, so I share IP with many other ISP customers and 2) changes IP very frequently.
A while back however I just locked down all ports on my "server" (really just an old computer in my home) and instead setup a CloudFlare Tunnel[1] on it. All it really does is instead of CloudFlare forwardning HTTP requests to your server, your server connects to CloudFlare and uses that connection for bi-directional communication of HTTP request/responses. I have an nginx web server listening on a UNIX socket that the local cloudflared daemon will forward traffic to, but nginx is not needed and you could instead set up an individual tunnel for each domain/subdomain you have, but I personally just re-use the same tunnel for everything.
Works really well even though I'm behind NAT, the public IP changes, or network goes down briefly; the cloudflared daemon just reconnects. No DNS updates needs to propagate either. I can understand though if some people are reluctant to using CloudFlare, but for me this is a lovely feature they have - and it's free.
It does, if you use e.g uBlock Origin. Twitch is actively trying to circumvent adblockers but so far hasn't been able to topple the more advanced blockers, even though they released SureStream[1] almost 4 years ago which is supposed to "weave" the ad directly into the source stream.
In the announcement[2] they mentioned:
> We are well aware that many dedicated Twitch viewers use software that bypasses ads, and the rollout of this technology will reduce the efficacy of such software. As a company we are agnostic when it comes to the use of this software. You are free to use it, or not, as you see fit.
I suspect that either advertisers or streamers aren't using SureStream or that it's quite resource expensive to "weave" ads into the source stream so Twitch simply isn't using it when the cost of doing so is more than it generates in ad revenue.
I'm using Firefox and all I have to do to view permissions for an extension is to go to about:addons and click an extension, and there will be a "Permissions"-tab that lists what permissions this extension requests. It's similar in Google Chrome IIRC. Other than that, both browsers lists the permissions an extension requests when installing the extension, and further when the extension requests new permissions.
How about Dota 2? There's nothing you can buy that can affect gameplay. Steam Workshop is used by community artists to submit their cosmetics, which gets voted into the game by the players. Steam Marketplace where players can buy, sell and trade their cosmetics. 25% of all Battle Pass[0] spending goes directly towards the prize pool of the yearly The International[1] esports tournament, with this year's tournament boasting a $34,330,068 USD prize pool, the largest to date[2].