Cancel scheduled evaluation on error (#1688)

This commit is contained in:
Jonatan Kłosko 2023-02-03 21:04:13 +01:00 committed by GitHub
parent c29b6fdda2
commit 597d9fff3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 337 additions and 57 deletions

View file

@ -280,6 +280,7 @@ const Session = {
faviconForEvaluationStatus(evaluationStatus) {
if (evaluationStatus === "evaluating") return "favicon-evaluating";
if (evaluationStatus === "stale") return "favicon-stale";
if (evaluationStatus === "errored") return "favicon-errored";
return "favicon";
},

View file

@ -68,11 +68,8 @@ defmodule Livebook.LiveMarkdown.Export do
end
defp notebook_metadata(notebook) do
put_unless_default(
%{},
Map.take(notebook, [:persist_outputs, :autosave_interval_s]),
Map.take(Notebook.new(), [:persist_outputs, :autosave_interval_s])
)
keys = [:persist_outputs, :autosave_interval_s]
put_unless_default(%{}, Map.take(notebook, keys), Map.take(Notebook.new(), keys))
end
defp render_section(section, notebook, ctx) do
@ -148,11 +145,8 @@ defmodule Livebook.LiveMarkdown.Export do
end
defp cell_metadata(%Cell.Code{} = cell) do
put_unless_default(
%{},
Map.take(cell, [:disable_formatting, :reevaluate_automatically]),
Map.take(Cell.Code.new(), [:disable_formatting, :reevaluate_automatically])
)
keys = [:disable_formatting, :reevaluate_automatically, :continue_on_error]
put_unless_default(%{}, Map.take(cell, keys), Map.take(Cell.Code.new(), keys))
end
defp cell_metadata(_cell), do: %{}

View file

@ -393,6 +393,9 @@ defmodule Livebook.LiveMarkdown.Import do
{"reevaluate_automatically", reevaluate_automatically}, attrs ->
Map.put(attrs, :reevaluate_automatically, reevaluate_automatically)
{"continue_on_error", continue_on_error}, attrs ->
Map.put(attrs, :continue_on_error, continue_on_error)
_entry, attrs ->
attrs
end)

View file

@ -6,7 +6,14 @@ defmodule Livebook.Notebook.Cell.Code do
# It consists of text content that the user can edit
# and produces some output once evaluated.
defstruct [:id, :source, :outputs, :disable_formatting, :reevaluate_automatically]
defstruct [
:id,
:source,
:outputs,
:disable_formatting,
:reevaluate_automatically,
:continue_on_error
]
alias Livebook.Utils
alias Livebook.Notebook.Cell
@ -16,7 +23,8 @@ defmodule Livebook.Notebook.Cell.Code do
source: String.t() | :__pruned__,
outputs: list(Cell.indexed_output()),
disable_formatting: boolean(),
reevaluate_automatically: boolean()
reevaluate_automatically: boolean(),
continue_on_error: boolean()
}
@doc """
@ -29,7 +37,8 @@ defmodule Livebook.Notebook.Cell.Code do
source: "",
outputs: [],
disable_formatting: false,
reevaluate_automatically: false
reevaluate_automatically: false,
continue_on_error: false
}
end
end

View file

@ -78,6 +78,7 @@ defprotocol Livebook.Runtime do
dependencies between evaluations and avoids unnecessary reevaluations.
"""
@type evaluation_response_metadata :: %{
errored: boolean(),
evaluation_time_ms: non_neg_integer(),
code_error: code_error(),
memory_usage: runtime_memory(),

View file

@ -466,6 +466,7 @@ defmodule Livebook.Runtime.Evaluator do
output = state.formatter.format_result(result)
metadata = %{
errored: elem(result, 0) == :error,
evaluation_time_ms: evaluation_time_ms,
memory_usage: memory(),
code_error: code_error,

View file

@ -88,11 +88,13 @@ defmodule Livebook.Session.Data do
@type cell_eval_info :: %{
validity: cell_evaluation_validity(),
status: cell_evaluation_status(),
errored: boolean(),
snapshot: snapshot(),
evaluation_digest: String.t() | nil,
evaluation_snapshot: snapshot() | nil,
evaluation_time_ms: integer() | nil,
evaluation_start: DateTime.t() | nil,
evaluation_end: DateTime.t() | nil,
evaluation_number: non_neg_integer(),
outputs_batch_number: non_neg_integer(),
bound_to_input_ids: MapSet.t(input_id()),
@ -1051,19 +1053,29 @@ defmodule Livebook.Session.Data do
end
defp finish_cell_evaluation(data_actions, cell, section, metadata) do
data_actions =
data_actions
|> update_cell_eval_info!(cell.id, fn eval_info ->
%{
eval_info
| status: :ready,
errored: metadata.errored,
evaluation_time_ms: metadata.evaluation_time_ms,
identifiers_used: metadata.identifiers_used,
identifiers_defined: metadata.identifiers_defined,
bound_to_input_ids: eval_info.new_bound_to_input_ids
bound_to_input_ids: eval_info.new_bound_to_input_ids,
evaluation_end: DateTime.utc_now()
}
end)
|> update_cell_evaluation_snapshot(cell, section)
|> set_section_info!(section.id, evaluating_cell_id: nil)
if metadata.errored and not Map.get(cell, :continue_on_error, false) do
data_actions
|> unqueue_child_cells_evaluation(cell)
else
data_actions
end
end
defp update_cell_evaluation_snapshot({data, _} = data_actions, cell, section) do
@ -1241,7 +1253,8 @@ defmodule Livebook.Session.Data do
data: data,
# This is a rough estimate, the exact time is measured in the
# evaluator itself
evaluation_start: DateTime.utc_now()
evaluation_start: DateTime.utc_now(),
evaluation_end: nil
}
end)
|> set_section_info!(section.id,
@ -1786,9 +1799,11 @@ defmodule Livebook.Session.Data do
%{
validity: :fresh,
status: :ready,
errored: false,
evaluation_digest: nil,
evaluation_time_ms: nil,
evaluation_start: nil,
evaluation_end: nil,
evaluation_number: 0,
outputs_batch_number: 0,
bound_to_input_ids: MapSet.new(),
@ -1797,7 +1812,8 @@ defmodule Livebook.Session.Data do
identifiers_defined: %{},
snapshot: nil,
evaluation_snapshot: nil,
data: nil
data: nil,
reevaluates_automatically: false
}
end
@ -1923,6 +1939,7 @@ defmodule Livebook.Session.Data do
data_actions
|> compute_snapshots()
|> update_validity()
|> update_reevaluates_automatically()
# After updating validity there may be new stale cells, so we check
# if any of them is configured for automatic reevaluation
|> maybe_queue_reevaluating_cells()
@ -2066,15 +2083,45 @@ defmodule Livebook.Session.Data do
end)
end
defp update_reevaluates_automatically({data, _} = data_actions) do
eval_parents = cell_evaluation_parents(data)
cells_with_section = Notebook.evaluable_cells_with_section(data.notebook)
cell_lookup = for {cell, _section} <- cells_with_section, into: %{}, do: {cell.id, cell}
reduce(data_actions, cells_with_section, fn data_actions, {cell, _section} ->
info = data.cell_infos[cell.id]
# Note that we disable automatic reevaluation if any evaluation
# parent errored (unless continue-on-error is enabled)
reevaluates_automatically =
Map.get(cell, :reevaluate_automatically, false) and
info.eval.validity in [:evaluated, :stale] and
not Enum.any?(eval_parents[cell.id], fn parent_id ->
errored? = data.cell_infos[parent_id].eval.errored
continue_on_error? = Map.get(cell_lookup[parent_id], :continue_on_error, false)
errored? and not continue_on_error?
end)
if reevaluates_automatically == info.eval.reevaluates_automatically do
data_actions
else
update_cell_eval_info!(
data_actions,
cell.id,
&%{&1 | reevaluates_automatically: reevaluates_automatically}
)
end
end)
end
defp maybe_queue_reevaluating_cells({data, _} = data_actions) do
cells_to_reevaluate =
data.notebook
|> Notebook.evaluable_cells_with_section()
|> Enum.filter(fn {cell, _section} ->
info = data.cell_infos[cell.id]
info.eval.status == :ready and info.eval.validity == :stale and
Map.get(cell, :reevaluate_automatically, false)
match?(%{status: :ready, validity: :stale, reevaluates_automatically: true}, info.eval)
end)
data_actions

View file

@ -2031,15 +2031,29 @@ defmodule LivebookWeb.SessionLive do
end
defp cells_status(cells, data) do
eval_infos =
for cell <- cells,
Cell.evaluable?(cell),
info = data.cell_infos[cell.id].eval,
do: Map.put(info, :id, cell.id)
most_recent =
eval_infos
|> Enum.filter(& &1.evaluation_end)
|> Enum.max_by(& &1.evaluation_end, DateTime, fn -> nil end)
cond do
evaluating = Enum.find(cells, &evaluating?(&1, data)) ->
evaluating = Enum.find(eval_infos, &(&1.status == :evaluating)) ->
{:evaluating, evaluating.id}
stale = Enum.find(cells, &stale?(&1, data)) ->
most_recent != nil and most_recent.errored ->
{:errored, most_recent.id}
stale = Enum.find(eval_infos, &(&1.validity == :stale)) ->
{:stale, stale.id}
evaluated = Enum.find(Enum.reverse(cells), &evaluated?(&1, data)) ->
{:evaluated, evaluated.id}
most_recent != nil ->
{:evaluated, most_recent.id}
true ->
{:fresh, nil}
@ -2055,18 +2069,6 @@ defmodule LivebookWeb.SessionLive do
cells_status(cells, data)
end
defp evaluating?(cell, data) do
get_in(data.cell_infos, [cell.id, :eval, :status]) == :evaluating
end
defp stale?(cell, data) do
get_in(data.cell_infos, [cell.id, :eval, :validity]) == :stale
end
defp evaluated?(cell, data) do
get_in(data.cell_infos, [cell.id, :eval, :validity]) == :evaluated
end
defp section_views(sections, data) do
sections
|> Enum.map(& &1.name)
@ -2142,6 +2144,8 @@ defmodule LivebookWeb.SessionLive do
outputs: cell.outputs,
validity: eval_info.validity,
status: eval_info.status,
errored: eval_info.errored,
reevaluates_automatically: eval_info.reevaluates_automatically,
evaluation_time_ms: eval_info.evaluation_time_ms,
evaluation_start: eval_info.evaluation_start,
evaluation_digest: encode_digest(eval_info.evaluation_digest),

View file

@ -68,6 +68,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
validity={@cell_view.eval.validity}
status={@cell_view.eval.status}
reevaluate_automatically={@cell_view.reevaluate_automatically}
reevaluates_automatically={@cell_view.eval.reevaluates_automatically}
/>
</:primary>
<:secondary>
@ -167,6 +168,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
validity={@cell_view.eval.validity}
status={@cell_view.eval.status}
reevaluate_automatically={false}
reevaluates_automatically={@cell_view.eval.reevaluates_automatically}
/>
</:primary>
<:secondary>
@ -307,7 +309,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
data-cell-id={@cell_id}
>
<%= cond do %>
<% @reevaluate_automatically and @validity in [:evaluated, :stale] -> %>
<% @reevaluates_automatically -> %>
<.remix_icon icon="check-line" class="text-xl" />
<span class="text-sm font-medium">Reevaluates automatically</span>
<% @validity == :evaluated -> %>
@ -653,7 +655,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
defp cell_status(%{cell_view: %{eval: %{validity: :evaluated}}} = assigns) do
~H"""
<.status_indicator
circle_class="bg-green-bright-400"
circle_class={if(@cell_view.eval.errored, do: "bg-red-400", else: "bg-green-bright-400")}
change_indicator={true}
tooltip={evaluated_label(@cell_view.eval.evaluation_time_ms)}
>

View file

@ -12,6 +12,7 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
|> assign(assigns)
|> assign_new(:disable_formatting, fn -> cell.disable_formatting end)
|> assign_new(:reevaluate_automatically, fn -> cell.reevaluate_automatically end)
|> assign_new(:continue_on_error, fn -> cell.continue_on_error end)
{:ok, socket}
end
@ -38,6 +39,13 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
checked={@reevaluate_automatically}
/>
</div>
<div class="w-full flex-col space-y-6 mt-4">
<.switch_checkbox
name="continue_on_error"
label="Continue on error"
checked={@continue_on_error}
/>
</div>
<div class="mt-8 flex justify-begin space-x-2">
<button class="button-base button-blue" type="submit">
Save
@ -54,16 +62,19 @@ defmodule LivebookWeb.SessionLive.CodeCellSettingsComponent do
"save",
%{
"enable_formatting" => enable_formatting,
"reevaluate_automatically" => reevaluate_automatically
"reevaluate_automatically" => reevaluate_automatically,
"continue_on_error" => continue_on_error
},
socket
) do
disable_formatting = enable_formatting == "false"
reevaluate_automatically = reevaluate_automatically == "true"
continue_on_error = continue_on_error == "true"
Session.set_cell_attributes(socket.assigns.session.pid, socket.assigns.cell.id, %{
disable_formatting: disable_formatting,
reevaluate_automatically: reevaluate_automatically
reevaluate_automatically: reevaluate_automatically,
continue_on_error: continue_on_error
})
{:noreply, push_patch(socket, to: socket.assigns.return_to)}

View file

@ -190,6 +190,21 @@ defmodule LivebookWeb.SessionLive.IndicatorsComponent do
"""
end
defp global_status(%{status: :errored} = assigns) do
~H"""
<span class="tooltip left" data-tooltip="Go to last evaluated cell">
<button
class="border-red-300 icon-button icon-outlined-button hover:bg-red-50 focus:bg-red-50"
aria-label="go to last evaluated cell"
data-el-focus-cell-button
data-target={@cell_id}
>
<.remix_icon icon="loader-3-line" class="text-xl text-red-400" />
</button>
</span>
"""
end
defp global_status(%{status: :stale} = assigns) do
~H"""
<span class="tooltip left" data-tooltip="Go to first stale cell">

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" id="svg2" xml:space="preserve" width="823.87854" height="901.65137" viewBox="0 0 823.87854 901.65133" sodipodi:docname="favicon-errored.svg" inkscape:version="1.2.2 (b0a84865, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>
image/svg+xml
</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath20">
<path d="M 0,805.333 H 3800 V 0 H 0 Z" id="path18"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath148">
<path d="M 353.619,544.464 H 573.136 V 440.053 H 353.619 Z" id="path146"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath172">
<path d="m 351.9,352.883 h 95.147 V 122.614 H 351.9 Z" id="path170"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1292">
<path id="path1294" style="fill:#000000;fill-opacity:1;stroke-width:7.50045;stroke-linecap:round;stroke-linejoin:round" d="m 202.05067,751.83844 c -9.972,0 -18,-8.028 -18,-18 V -10.124963 c 0,-9.971978 8.028,-17.999978 18,-17.999978 h 710.98537 c 9.972,0 18,8.028 18,17.999978 V 733.83844 c 0,9.972 -8.028,18 -18,18 z M 696.25088,372.24907 a 187.35425,187.35425 0 0 0 0.2417,0 A 187.35425,187.35425 0 0 0 883.84609,184.89408 187.35425,187.35425 0 0 0 696.49258,-2.4594376 187.35425,187.35425 0 0 0 509.1376,184.89408 187.35425,187.35425 0 0 0 696.25088,372.24907 Z"/>
</clipPath>
</defs>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1027" id="namedview4" showgrid="false" inkscape:zoom="0.48408519" inkscape:cx="480.28736" inkscape:cy="837.66248" inkscape:window-x="584" inkscape:window-y="25" inkscape:window-maximized="0" inkscape:current-layer="g10" inkscape:document-rotation="0" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1"/>
<g id="g10" inkscape:groupmode="layer" inkscape:label="editable_Livebook_Logo" transform="matrix(1.3333333,0,0,-1.3333333,-271.62159,986.33208)">
<g id="g14" clip-path="url(#clipPath1292)">
<g id="g16" clip-path="url(#clipPath20)">
<g id="g118" transform="translate(425.3255,680.1453)">
<path d="M 0,0 C 27.577,33.093 44.051,35.111 44.303,34.988 53.563,33.872 54.75,25.563 54.75,25.563 L 48.815,7.759 36.397,-25.372 c -12.991,2.166 -50.193,4.688 -52.949,3.31 z" style="fill:#825161;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path120"/>
</g>
<g id="g122" transform="translate(465.032,662.4954)">
<path d="m 0,0 c 0,0 1.103,3.31 3.309,9.928 0.149,0.446 -33.093,5.515 -55.154,-5.515 0,0 33.092,2.206 51.845,-4.413" style="fill:#563642;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path124"/>
</g>
<g id="g126" transform="translate(416.4956,669.1141)">
<path d="m 0,0 5.638,59.34 c -1.225,10.155 -7.844,14.568 -18.421,8.598 -1.514,-0.854 -3.384,-2.171 -4.941,-3.407 -37.828,-30.007 -44.168,-81.382 -45.215,-106.367 0.017,-0.925 -0.012,-5.787 -0.051,-6.7 -0.989,-51.846 68.077,-90.357 87.258,-109.207 -3.976,7.409 -33.71,32.402 -37.505,50.743 -2.915,14.088 0.033,25.5 4.619,34.123 5.83,10.959 15.579,19.332 27.085,23.993 29.794,12.071 59.031,10.045 84.121,5.863 38.691,-6.448 69.005,-22.321 70.537,-23.132 0.041,-0.022 0.061,-0.033 0.061,-0.033 -0.028,0.014 1.707,6.516 0,9.928 -2.206,4.413 -7.109,6.373 -7.868,6.778 C 153.087,-42.987 98.646,-13.885 51.183,-3.768 54.052,-4.412 25.371,2.207 0,0" style="fill:#825161;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path128"/>
</g>
<g id="g130" transform="translate(525.7024,616.1653)">
<path d="m 0,0 c -25.09,4.182 -50.834,4.135 -80.628,-7.936 -11.505,-4.661 -21.747,-14.412 -27.577,-25.371 -4.587,-8.622 -6.517,-17.435 -3.208,-33.982 2.632,-13.159 8.825,-26.474 14.341,-35.299 22.621,-18.788 30.59,-16.362 56.257,-31.99 -7.706,37.317 0.852,89.735 89.315,110.745 0.087,0.021 7.234,1.567 7.516,1.687 3.934,1.668 6.669,5.099 7.922,8.931 C 63.067,-12.751 39.14,-6.523 0,0" style="fill:#c4adb5;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path132"/>
</g>
<g id="g134" transform="translate(589.6821,607.3406)">
<path d="m 0,0 c -13.237,4.413 -31.99,9.928 -57.708,16.292 -29.298,7.249 -64.537,10.376 -90.107,0.254 0,0 -58.464,-16.546 -38.609,-77.216 l 3.31,-13.237 c 0,0 6.581,0.003 13.812,-5.112 -1.147,3.495 -5.422,17.002 -6.132,20.567 -3.298,16.556 0.033,25.515 4.604,34.142 5.81,10.966 15.527,19.344 26.994,24.008 6.942,2.824 13.999,4.993 21.056,6.608 4.301,0.984 8.665,1.64 13.064,1.989 21.894,1.741 50.539,1.716 107.51,-12.707 l 0.154,0.464 c 0.948,-0.273 1.421,-0.421 1.421,-0.421 C -0.589,-4.391 0.177,-0.059 0,0" style="fill:#4c313b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path136"/>
</g>
<g id="g138" transform="translate(354.1892,303.2845)">
<path d="m 0,0 c -0.044,-10.08 2.465,-20.009 7.354,-28.825 9.226,-16.633 29.838,-42.629 75.711,-72.288 52.438,-33.904 146.912,-63.851 146.912,-63.851 l 13.238,291.422 c 0,0 -243.836,115.46 -243.836,197.646 C -0.621,326.15 0.245,55.466 0,0" style="fill:#dbd8d5;fill-opacity:1;fill-rule:evenodd;stroke:none" id="path140"/>
</g>
<g id="g142">
<g id="g144"/>
<g id="g156">
<g clip-path="url(#clipPath148)" opacity="0.25" id="g154">
<g transform="translate(359.1348,529.0208)" id="g152">
<path d="m 0,0 214.001,-88.248 v 1.103 L -5.516,15.443 V 2.206 c 0,0 0.102,-0.038 5.516,-2.206" style="fill:#36272b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path150"/>
</g>
</g>
</g>
</g>
<g id="g158" transform="translate(293.3445,165.7054)">
<path d="m 0,0 c -3.146,1.255 -5.206,4.305 -5.196,7.692 l 0.424,376.462 c -0.11,2.985 0.066,7.804 5.48,5.636 L 262.142,286.099 c 0,-3.309 0.015,-2.127 0,-5.516 l -0.648,-371.921 c -0.021,-4.584 0.648,-7.544 0.648,-10.854 C 262.142,-102.82 0,0 0,0" style="fill:#c24063;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path160"/>
</g>
<g id="g162" transform="translate(555.4861,63.5135)">
<path d="m 0,0 260.943,102.192 c 3.146,1.255 5.206,4.305 5.196,7.692 l -0.424,376.462 c 0.061,1.642 -0.271,3.506 -1.483,4.489 -0.99,0.804 -2.569,1.019 -5.004,0.043 L 0,388.291 c 0,-4.413 -0.015,-2.127 0,-5.516 L -0.551,10.854 C -0.525,5.032 0,3.31 0,0" style="fill:#ff87a7;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path164"/>
</g>
<g id="g166">
<g id="g168"/>
<g id="g180">
<g clip-path="url(#clipPath172)" opacity="0.25" id="g178">
<g transform="translate(396.6399,125.2874)" id="g176">
<path d="m 0,0 c 0,0 25.629,-13.707 41.918,20.958 16.816,35.79 11.031,138.991 -39.073,206.638 0,0 33.557,-168.029 -47.585,-209.748 z" style="fill:#36272b;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path174"/>
</g>
</g>
</g>
</g>
<g id="g182" transform="translate(235.1873,329.8465)">
<path d="m 0,0 -6.929,-91.382 c -1.889,-24.9 6.023,-49.032 22.274,-67.955 16.147,-18.923 39.281,-30.415 63.952,-32.286 5.208,-0.395 10.484,-0.359 15.781,0.124 44.579,4.068 80.894,42.703 84.471,89.866 l 3.337,44.017 C 187.631,4.953 156.231,63.07 102.889,90.441 59.026,112.964 14.225,107.04 -30.368,72.879 -9.406,54.897 2.1,27.688 0,0" style="fill:#1f2728;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path184"/>
</g>
<g id="g186" transform="translate(311.2022,170.5091)">
<path d="m 0,0 c -16.25,18.924 -24.162,43.056 -22.274,67.956 l 6.93,91.381 c 2.099,27.689 -9.406,54.898 -30.368,72.879 16.567,12.692 33.162,21.478 49.728,26.364 -36.867,10.985 -74.257,2.169 -111.502,-26.364 20.962,-17.981 32.468,-45.19 30.368,-72.879 l -6.929,-91.381 c -1.889,-24.9 6.023,-49.032 22.274,-67.956 16.147,-18.922 39.281,-30.415 63.952,-32.286 5.208,-0.395 10.484,-0.359 15.781,0.125 7.571,0.691 14.895,2.4 21.87,4.958 C 24.539,-21.832 10.724,-12.567 0,0" style="fill:#2f393d;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path188"/>
</g>
</g>
</g>
<circle style="fill:#e97579;fill-opacity:1;stroke-width:10.4152;stroke-linecap:round;stroke-linejoin:round" id="path1157" cx="698.31262" cy="-185.48764" transform="scale(1,-1)" r="114.5602"/>
<path fill="none" d="m -530.12015,778.48595 h 18 v -18 h -18 z" id="path2" style="stroke-width:0.75"/>
<path d="m -522.62015,767.10695 6.894,6.89475 1.06125,-1.0605 -7.95525,-7.95525 -4.773,4.773 1.0605,1.0605 z" id="path4" style="stroke-width:0.75"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -29,6 +29,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
Notebook.Cell.new(:code)
| disable_formatting: true,
reevaluate_automatically: true,
continue_on_error: true,
source: """
Enum.to_list(1..10)\
"""
@ -101,7 +102,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
$x_{i} + y_{i}$
<!-- livebook:{"disable_formatting":true,"reevaluate_automatically":true} -->
<!-- livebook:{"continue_on_error":true,"disable_formatting":true,"reevaluate_automatically":true} -->
```elixir
Enum.to_list(1..10)

View file

@ -19,7 +19,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
$x_{i} + y_{i}$
<!-- livebook:{"disable_formatting":true,"reevaluate_automatically":true} -->
<!-- livebook:{"continue_on_error":true,"disable_formatting":true,"reevaluate_automatically":true} -->
```elixir
Enum.to_list(1..10)
@ -80,6 +80,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
%Cell.Code{
disable_formatting: true,
reevaluate_automatically: true,
continue_on_error: true,
source: """
Enum.to_list(1..10)\
"""

View file

@ -15,7 +15,14 @@ defmodule Livebook.Session.DataTest do
defp eval_meta(opts \\ []) do
uses = opts[:uses] || []
defines = opts[:defines] || %{}
%{evaluation_time_ms: 10, identifiers_used: uses, identifiers_defined: defines}
errored = Keyword.get(opts, :errored, false)
%{
errored: errored,
evaluation_time_ms: 10,
identifiers_used: uses,
identifiers_defined: defines
}
end
describe "new/1" do
@ -2038,8 +2045,7 @@ defmodule Livebook.Session.DataTest do
])
operation =
{:add_cell_evaluation_response, @cid, "c1", @eval_resp,
eval_meta(%{defines: %{"c1" => 1}})}
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(defines: %{"c1" => 1})}
assert {:ok,
%{
@ -2173,6 +2179,98 @@ defmodule Livebook.Session.DataTest do
}, _actions} = Data.apply_operation(data, operation)
end
test "unqueues child cells if the evaluation errored" do
data =
data_after_operations!([
{:insert_section, @cid, 0, "s1"},
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
{:insert_section, @cid, 1, "s2"},
{:insert_cell, @cid, "s2", 0, :code, "c3", %{}},
{:insert_cell, @cid, "s2", 1, :code, "c4", %{}},
{:set_runtime, @cid, connected_noop_runtime()},
evaluate_cells_operations(["setup"]),
{:queue_cells_evaluation, @cid, ["c1", "c2", "c3", "c4"]}
])
operation =
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)}
assert {:ok,
%{
cell_infos: %{
"c2" => %{eval: %{status: :ready, validity: :fresh}},
"c3" => %{eval: %{status: :ready, validity: :fresh}},
"c4" => %{eval: %{status: :ready, validity: :fresh}}
},
section_infos: %{
"s1" => %{evaluating_cell_id: nil},
"s2" => %{evaluating_cell_id: nil}
}
}, []} = Data.apply_operation(data, operation)
end
test "disables child cells automatic reevaluation if the evaluation errored" do
data =
data_after_operations!([
{:insert_section, @cid, 0, "s1"},
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
{:insert_cell, @cid, "s1", 2, :code, "c3", %{}},
{:set_cell_attributes, @cid, "c3", %{reevaluate_automatically: true}},
{:set_runtime, @cid, connected_noop_runtime()},
evaluate_cells_operations(["setup", "c1", "c2", "c3"],
uses: %{"c2" => ["c1"], "c3" => ["c2"]}
),
{:queue_cells_evaluation, @cid, ["c1"]}
])
operation =
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)}
assert {:ok,
%{
cell_infos: %{
"c2" => %{eval: %{status: :ready}},
"c3" => %{eval: %{status: :ready, reevaluates_automatically: false}}
},
section_infos: %{
"s1" => %{evaluating_cell_id: nil}
}
}, []} = Data.apply_operation(data, operation)
end
test "re-enables child cells automatic reevaluation if errored evaluation is fixed" do
data =
data_after_operations!([
{:insert_section, @cid, 0, "s1"},
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
{:insert_cell, @cid, "s1", 1, :code, "c2", %{}},
{:insert_cell, @cid, "s1", 2, :code, "c3", %{}},
{:set_cell_attributes, @cid, "c3", %{reevaluate_automatically: true}},
{:set_runtime, @cid, connected_noop_runtime()},
evaluate_cells_operations(["setup", "c1", "c2", "c3"],
uses: %{"c2" => ["c1"], "c3" => ["c2"]}
),
{:queue_cells_evaluation, @cid, ["c1"]},
{:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta(errored: true)},
{:queue_cells_evaluation, @cid, ["c1"]}
])
operation = {:add_cell_evaluation_response, @cid, "c1", @eval_resp, eval_meta()}
assert {:ok,
%{
cell_infos: %{
"c2" => %{eval: %{status: :evaluating}},
"c3" => %{eval: %{status: :queued, reevaluates_automatically: true}}
},
section_infos: %{
"s1" => %{evaluating_cell_id: "c2"}
}
}, _actions} = Data.apply_operation(data, operation)
end
test "if bound input value changes during cell evaluation, the cell is marked as stale afterwards" do
input = %{id: "i1", type: :text, label: "Text", default: "hey"}

View file

@ -7,7 +7,12 @@ defmodule Livebook.SessionTest do
alias Livebook.Notebook.{Section, Cell}
alias Livebook.Session.Data
@eval_meta %{evaluation_time_ms: 10, identifiers_used: [], identifiers_defined: %{}}
@eval_meta %{
errored: false,
evaluation_time_ms: 10,
identifiers_used: [],
identifiers_defined: %{}
}
setup do
session = start_session()