39 ms·
The real "aha!" moment for me with LiveView came when I was using LaTeX to render a PDF in response to some user actions. The first time I did something like th
by ashton314 3y ago
The real "aha!" moment for me with LiveView came when I was using LaTeX to render a PDF in response to some user actions. The first time I did something like this, I did some janky polling on the client side. Other (bad) solutions involved blocking the client process or just praying that the PDF would render in time before the HTTP request timed out.
With LiveView, it works like this:
- user pushes a button to render the PDF
- the LV process calls `Task.async(fn -> do_work({data, self.pid()}))` so the worker process has the PID of the LiveView process itself
- when the job completes on a different thread, the worker sends an Elixir message using the standard and highly-flexible built-in mechanism with `send()` to the PID of the LV
- the LV gets the message and updates the UI and/or initiates a download of the rendered PDF automatically
It is so simple to get this workflow going. It's super efficient. Perfect for low- to mid-range load on a web app. Perfect for one-person development.
- throwawaymaths 3y agoTask.async already implements a result message so you're reduplicating the effort hete. You can listen for the task.async callback from LiveView.handle_info directly, instead of having the asynchronously run function send back the message. You may want to store the ref and pid of the task in the assigns. From the docs: "Alternatively, if you spawn a task [via async] inside a GenServer, then the GenServer will automatically await for you and call GenServer.handle_info/2 with the task response..." Also if you prefer sequencing the response manually the way you're doing, Task.start_link is probably more appropriate. Note that the way you're doing it now your liveview's message queue will leak with unhandled Task.async responses and down messages (probably not a big deal given your problem domain). If you do go the Task.start_link way, you do get two way binding (if the task dies the liveview will restart itself).
- ashton314 3y agoOk, that all sounds much better. Thanks for the reply! It’s been a hot minute since I’ve used LV—so much good stuff happening. I think it’s a testament to the power of the platform that the obvious dumb solution worked so well for me. Lol
- throwawaymaths 3y agoAlways do the obvious dumb solution first!!
- deleted 3y ago[deleted]
- ransackdev 3y agoHow does this work when there are many processes running livebook, behind a web server reverse proxy like nginx, there is no sticky sessions in place (because hot spots on certain upstreams kinda defeats the purpose of a load balancer), and the user that kicked off that async pdf job on PID 1, inside container 1, refreshes their browser and is now connecting to container 2, which is running a different Linux process than where the pdf task is running in a thread? Now add in the fact that container might be on a different node/vm in the cluster, different AZ, or different region/data center. Background worker systems like Oban or sidekiq are where you want to run your async work imo. You can still do a pub/sub type notifier model to bubble up the job’s completion, or poll. Polling is perfectly acceptable for most apps and traffic levels and is simpler to mentally grok compared to websockets and persistent connections. Websockets require a central broker to pass messages through when you scale past one process and with elixir the options are the redis pubsub adapter, or for container based deployments or those wanting to use the built in distributed erlang functions, libcluster. More complexity. More points of failure. More potential centralized bottlenecks. More things to mentally model when debugging. More dev and prod drift. Just poll, until you can’t scale your web servers any more. It’s just an http request. It’s already implemented if you’re serving web traffic. It’s not clear based on what you wrote but are the pdf’s contents being passed in the complete message? That sounds like an OOM termination that’s hard asf to track down just waiting to happen. PDF should go into blob storage somewhere and the task should bubble up a complete message, which then is used to signal the retrieval and proxying of the file to the user. If you have to background the pdf generation, it’s slow. If it’s slow to generate a document, it’s large. You don’t want that to be pushed through memory because your system stability is then coupled to not only the contents of a document that could exceed the memory of the system, but also is at the mercy of how many users/requests are all generating pdfs at the same time, and of course you’ll have that one guy spamming the “generate” button and queuing up 300 jobs because we didn’t need indicators on the UI and who adds uniqueness constraints to internal tools ;) Disks and file systems are for passing very large messages on a system
- iudqnolq 3y ago> How does this work when ... there is no sticky sessions in place (because hot spots on certain upstreams kinda defeats the purpose of a load balancer), and the user that kicked off that async pdf job on PID 1, inside container 1, refreshes their browser and is now connecting to container 2, which is running a different Linux process than where the pdf task is running in a thread? The user destroys their first liveview instance and gets a new liveview instance. You may not have properly set up bidirectional links so the destruction of this first liveview kills the first background job, or it may run to completion and be discarded. This might be exactly the semantics the user wants. If the user expects state to stick around that would need to be implemented separately. > If you have to background the pdf generation, it’s slow. If it’s slow to generate a document, it’s large. I'm not sure it's quite that slow. It's too slow to freeze the entire UI while it generates. But it's fast enough blocking a http response until it finishes often works. A simple system for transient background processing like Task.async might be appropriate. > Disks and file systems are for passing very large messages on a system We don't know what the type of the response message is. It could well be a filename in a temporary dirextory. That would be the simplest way to implement a background task that shells out.