diff --git a/lib/livebook/runtime/evaluator/io_proxy.ex b/lib/livebook/runtime/evaluator/io_proxy.ex index 7dd7d13ee..71123369f 100644 --- a/lib/livebook/runtime/evaluator/io_proxy.ex +++ b/lib/livebook/runtime/evaluator/io_proxy.ex @@ -234,6 +234,12 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do {:ok, state} end + defp io_request({:livebook_put_output_to_clients, output}, state) do + state = flush_buffer(state) + send(state.send_to, {:runtime_evaluation_output_to_clients, state.ref, output}) + {:ok, state} + end + defp io_request({:livebook_get_input_value, input_id}, state) do input_cache = Map.put_new_lazy(state.input_cache, input_id, fn -> diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index 5eba2492b..428323dc5 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -1307,9 +1307,12 @@ defmodule Livebook.Session do send(client_pid, {:operation, operation}) # Keep track of assets infos, so we can look them up when fetching - for assets_info <- Cell.find_assets_in_output(output), reduce: state do - state -> put_in(state.client_id_with_assets[client_id][assets_info.hash], assets_info) - end + new_asset_infos = + for assets_info <- Cell.find_assets_in_output(output), + into: %{}, + do: {assets_info.hash, assets_info} + + update_in(state.client_id_with_assets[client_id], &Map.merge(&1, new_asset_infos)) else state end @@ -1317,6 +1320,27 @@ defmodule Livebook.Session do {:noreply, state} end + def handle_info({:runtime_evaluation_output_to_clients, cell_id, output}, state) do + operation = {:add_cell_evaluation_output, @client_id, cell_id, output} + broadcast_operation(state.session_id, operation) + + # Keep track of assets infos, so we can look them up when fetching + new_asset_infos = + for assets_info <- Cell.find_assets_in_output(output), + into: %{}, + do: {assets_info.hash, assets_info} + + state = + update_in( + state.client_id_with_assets, + &Map.new(&1, fn {client_id, asset_infos} -> + {client_id, Map.merge(asset_infos, new_asset_infos)} + end) + ) + + {:noreply, state} + end + def handle_info({:runtime_evaluation_response, cell_id, response, metadata}, state) do {memory_usage, metadata} = Map.pop(metadata, :memory_usage) operation = {:add_cell_evaluation_response, @client_id, cell_id, response, metadata} diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index 1ab908690..e923fb19e 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -496,6 +496,31 @@ defmodule LivebookWeb.SessionLiveTest do {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") refute render(view) =~ "line 1" end + + test "clients-only output is sent to all targets, but not reflected in session", + %{conn: conn, session: session} do + user1 = build(:user, name: "Jake Peralta") + Session.register_client(session.pid, self(), user1) + + Session.subscribe(session.id) + evaluate_setup(session.pid) + + section_id = insert_section(session.pid) + cell_id = insert_text_cell(session.pid, section_id, :code) + + Session.queue_cell_evaluation(session.pid, cell_id) + + send( + session.pid, + {:runtime_evaluation_output_to_clients, cell_id, {:stdout, "line 1\n"}} + ) + + assert_receive {:operation, + {:add_cell_evaluation_output, _, ^cell_id, {:stdout, "line 1\n"}}} + + {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") + refute render(view) =~ "line 1" + end end describe "smart cells" do