1818-export ([process_task /3 ]).
1919-export ([continuation_task /3 ]).
2020-export ([next_task /1 ]).
21+ -export ([process_scheduled_task /3 ]).
2122
2223-record (prg_worker_state , {ns_id , ns_opts , process , sidecar_pid }).
2324
2425-define (DEFAULT_RANGE , #{direction => forward }).
26+ -define (CAPTURE_DEFENSE_INTERVAL_MS , 100 ).
2527
2628% %%
2729% %% API
@@ -39,6 +41,10 @@ continuation_task(Worker, TaskHeader, Task) ->
3941next_task (Worker ) ->
4042 gen_server :cast (Worker , next_task ).
4143
44+ -spec process_scheduled_task (pid (), id (), task_id ()) -> ok .
45+ process_scheduled_task (Worker , ProcessId , TaskId ) ->
46+ gen_server :cast (Worker , {process_scheduled_task , ProcessId , TaskId }).
47+
4248% %%===================================================================
4349% %% Spawning and gen_server implementation
4450% %%===================================================================
@@ -90,6 +96,32 @@ handle_cast(
9096 Deadline = erlang :system_time (millisecond ) + TimeoutSec * 1000 ,
9197 NewState = do_process_task (TaskHeader , Task , Deadline , State ),
9298 {noreply , NewState };
99+ handle_cast (
100+ {process_scheduled_task , ProcessId , TaskId },
101+ # prg_worker_state {
102+ ns_id = NsId ,
103+ ns_opts = #{storage := StorageOpts , process_step_timeout := TimeoutSec } = _NsOpts ,
104+ sidecar_pid = Pid
105+ } = State
106+ ) ->
107+ try prg_storage :capture_task (StorageOpts , NsId , TaskId ) of
108+ [] ->
109+ % % task cancelled, blocked, already running or finished
110+ ok = next_task (self ()),
111+ {noreply , State };
112+ [#{status := <<" running" >>} = Task ] ->
113+ Deadline = erlang :system_time (millisecond ) + TimeoutSec * 1000 ,
114+ HistoryRange = maps :get (range , maps :get (metadata , Task , #{}), #{}),
115+ {ok , Process } = prg_worker_sidecar :get_process (Pid , Deadline , StorageOpts , NsId , ProcessId , HistoryRange ),
116+ TaskHeader = create_header (Task ),
117+ NewState = do_process_task (TaskHeader , Task , Deadline , State # prg_worker_state {process = Process }),
118+ {noreply , NewState }
119+ catch
120+ Class :Term :Stacktrace ->
121+ logger :error (" process ~p . task capturing exception: ~p " , [ProcessId , [Class , Term , Stacktrace ]]),
122+ ok = next_task (self ()),
123+ {noreply , State }
124+ end ;
93125handle_cast (next_task , # prg_worker_state {sidecar_pid = CurrentPid }) ->
94126 % % kill sidecar and restart to clear memory
95127 true = erlang :unlink (CurrentPid ),
@@ -235,7 +267,9 @@ success_and_continue(Intent, TaskHeader, Task, Deadline, State) ->
235267 ),
236268 _ = maybe_reply (TaskHeader , Response ),
237269 case SaveResult of
238- {ok , []} ->
270+ {ok , [#{status := <<" waiting" >>, task_id := NextTaskId , scheduled_time := Ts } | _ ]} ->
271+ RunAfterMs = (Ts - Now ) * 1000 - ? CAPTURE_DEFENSE_INTERVAL_MS ,
272+ ok = prg_scheduler :schedule_task (NsId , ProcessId , NextTaskId , RunAfterMs ),
239273 ok = next_task (self ()),
240274 State # prg_worker_state {process = undefined };
241275 {ok , [ContinuationTask | _ ]} ->
@@ -383,6 +417,7 @@ success_and_unlock(Intent, TaskHeader, Task, Deadline, State) ->
383417 process = #{process_id := ProcessId , status := OldStatus } = Process ,
384418 sidecar_pid = Pid
385419 } = State ,
420+ Now = erlang :system_time (second ),
386421 {#{status := NewStatus } = ProcessUpdated , Updates } = update_process (Process , Intent ),
387422 ok = prg_worker_sidecar :lifecycle_sink (
388423 Pid , Deadline , NsOpts , lifecycle_event (TaskHeader , OldStatus , NewStatus ), ProcessId
@@ -409,7 +444,12 @@ success_and_unlock(Intent, TaskHeader, Task, Deadline, State) ->
409444 {ok , []} ->
410445 ok = next_task (self ()),
411446 State # prg_worker_state {process = undefined };
412- {ok , [ContinuationTask | _ ]} ->
447+ {ok , [#{status := <<" waiting" >>, task_id := NextTaskId , scheduled_time := Ts } | _ ]} ->
448+ RunAfterMs = (Ts - Now ) * 1000 - ? CAPTURE_DEFENSE_INTERVAL_MS ,
449+ ok = prg_scheduler :schedule_task (NsId , ProcessId , NextTaskId , RunAfterMs ),
450+ ok = next_task (self ()),
451+ State # prg_worker_state {process = undefined };
452+ {ok , [#{status := <<" running" >>} = ContinuationTask | _ ]} ->
413453 NewHistory = maps :get (history , Process ) ++ Events ,
414454 ok = continuation_task (self (), create_header (ContinuationTask ), ContinuationTask ),
415455 State # prg_worker_state {
@@ -475,7 +515,17 @@ error_and_retry({error, Reason} = Response, TaskHeader, Task, Deadline, State) -
475515 );
476516 NewTask ->
477517 Updates = #{process_id => ProcessId },
478- {ok , _ } = prg_worker_sidecar :complete_and_continue (
518+ % % prg_storage guarantees that when saving a task with the error status,
519+ % % all deferred tasks of all types will be completed with the canceled status,
520+ % % so calling complete_and_continue is guaranteed to return the retrieval task,
521+ % % and not any other deferred task
522+ {ok , [
523+ #{
524+ status := <<" waiting" >>,
525+ task_id := NextTaskId ,
526+ scheduled_time := Ts
527+ }
528+ ]} = prg_worker_sidecar :complete_and_continue (
479529 Pid ,
480530 Deadline ,
481531 StorageOpts ,
@@ -484,7 +534,10 @@ error_and_retry({error, Reason} = Response, TaskHeader, Task, Deadline, State) -
484534 Updates ,
485535 [],
486536 NewTask
487- )
537+ ),
538+ Now = erlang :system_time (second ),
539+ RunAfterMs = (Ts - Now ) * 1000 - ? CAPTURE_DEFENSE_INTERVAL_MS ,
540+ ok = prg_scheduler :schedule_task (NsId , ProcessId , NextTaskId , RunAfterMs )
488541 end ,
489542 ok = next_task (self ()),
490543 State # prg_worker_state {process = undefined }.
0 commit comments