Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Livebook hangs when handling large source smart cells #2916

Open
fahchen opened this issue Jan 25, 2025 · 3 comments
Open

Livebook hangs when handling large source smart cells #2916

fahchen opened this issue Jan 25, 2025 · 3 comments

Comments

@fahchen
Copy link

fahchen commented Jan 25, 2025

Hey guys, thank you for the amazing work you’re doing here. I really appreciate it!

I recently created a kino smart cell that allows to draw excalidraw graphs in the Livebook. You can check it out at https://github.com/fahchen/kino_excalidraw. However, I’ve run into an issue where the Livebook sometimes hangs. After some investigation, I found that Livebook.Text.Delta.diff/2 takes a significant amount of time to compute, especially as increasing as the source of the smart cell grows larger. Actually, I embed the excalidraw file which is in JSON format, into the notebook. Here’s the relevant part of the code:

delta = Livebook.Text.Delta.diff(cell.source, source)

I’d like to ask for your help with following:

  1. is this a proper way to embed the excalidraw into the livebook notebook?
  2. wow can I address this issue? My current solution is to add an option to generate Kino.nothing() as the source.

livebook-diff-issue.md (GitHub doesn’t allow uploading .livemd files, so I changed it to .md.)
Here’s an example livebook note that embeds an excalidraw graph, alternatively you can find an example from the kino_excaildraw document(https://hexdocs.pm/kino_excalidraw/components.html).

To reproduce this issue, you can open the example notebook from the above, draw a complex graph, and then Livebook will hang.

Thanks in advance for any help or suggestions.

@jonatanklosko
Copy link
Member

@fahchen what code is the smart cell supposed to generate? Looking at the source I see the generate code does KinoExcalidraw.SmartCell.new, but then you implement Kino.Render for that struct that renders nothing. Is it for use later?

@fahchen
Copy link
Author

fahchen commented Jan 30, 2025

@jonatanklosko Thanks for your reply.

what code is the smart cell supposed to generate?

The smart cell consists of two parts: data and options, sso I thought the generated code should include both parts for restoration.
I found that the Excalidraw canvas can be restored regardless of what to_source/1 returns. Should I generate source code that includes data and options for restoration, or is it fine to return nothing since the Excalidraw canvas can still be restored? But the generated source might have a use case—some later cells could store the Excalidraw canvas data in a file or upload it somewhere. But who knows?

Here is what the currently generated code looks like:

excalidraw =
  KinoExcalidraw.SmartCell.new(
    data: json_string,
    options: options_map
  )

The data is used to restore the Excalidraw graph when rendering, but its size can be quite large, depending on the complexity of the graph the user draws. This is where Livebook can hang while rendering the smart cell.

Looking at the source, I see that the generated code calls KinoExcalidraw.SmartCell.new,
but then you implement Kino.Render for that struct, which renders nothing.

Earlier, I noticed that Livebook was rendering the Excalidraw canvas twice, so I implemented Kino.Render to hide the canvas rendered by the output of this smart cell. However, after testing again, I found that removing Kino.Render no longer causes the canvas to be duplicated. It seems I misunderstood something before.

@jonatanklosko
Copy link
Member

The smart cell consists of two parts: data and options, sso I thought the generated code should include both parts for restoration.

The smart cell state returned by to_attrs(ctx) is stored in .livemd as a comment. Once the .livemd is opened and the smart cell starts, the init(attrs, ctx) callback is called with the same attributes. So this already handles "restoration" for you.

The main idea for smart cells is to generate code, but here you effectively want a Kino.JS.Live component with state persisted in the notebook itself, which a smart cell allows you to do. In that case generating code that returns Kino.nothing() sounds good.


I think we could change the diff, such that for large payloads it does a replace instead of computing diff (which is a tradeoff, we do less work, but may end up sending more data around and over the wire). That said, including extremely strings in code cells is not necessarily a good idea in the first place, it would make more sense to use files. For your use case, people could export the .excalidraw file, add it to notebooks file attachments (in the sidebar) and use Kino.FS.path to access it. Or, if they export .png, they can add it to notebook files and insert directly as an image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants