- Push a script to the stack and watch it close
- Plan reminders at J+7, J+15 and J+30, then cancel them
- Priorities and parallel limit of the stack
- A run fails, the API comes back, replay it
- Replay a whole batch of failed exports
- Track the steps of a long import with a stack flow
- Dashboard of the stack queue
- A dispatcher pushes child runs and you follow the whole tree
- Clean the stack after a test campaign
- Same script: execute now or hand it to the stack
Push a script to the stack and watch it close
An invoice must be computed in the background instead of blocking the caller. The script pushes a typed exe script to the stack with the date now, reads the waiting entry, waits for the stack cycle, then reads the closed entry and the activity log of that process.
#1. A worker script: typed parameters, activity log on, 1 attempt; script create exe "tuto.as.invoice" true 1 (param (var "[order]" {type is_number [order]} "Order number" is_null:false is_empty:false "1001") (var "[amount]" {type is_double [amount]} "Net amount" is_null:false is_empty:false "250.00") ;) "Compute the gross amount of an order in the background" { -> "[gross]" (math round (* [amount] 1.2) 2); log write (concat "invoice " [order] " gross " [gross]) "OK" "order" [order]; sequence increment "tuto_as_invoice_done"; [gross]; } "The gross amount"; sequence add "tuto_as_invoice_done" 0; #2. Push it now: stack returns the process id; -> "[pid]" (stack (date now) "tuto.as.invoice.exe" "[order]" 1001 "[amount]" 250); json load "tuto_as_p" (stack get [pid]); -> "[before]" (concat "state=" (json select "tuto_as_p" "/state") ", attempts=" (json select "tuto_as_p" "/nbattempt") "/" (json select "tuto_as_p" "/nbmaxattempt")); #3. The stack runs every 30 s: poll the counter written by the script; -> "[i]" 0; while (and (< [i] 40) (== (sequence get current "tuto_as_invoice_done") 0)) {wait "1000"; ++ "[i]";}; json load "tuto_as_p" (stack get [pid]); json load "tuto_as_log" (string substring (log show_time [pid]) 39); -> "[out]" (concat "pid " [pid] " pushed, " [before] [_n_] "closed at " (string substring (json select "tuto_as_p" "/dtclosed") 0 19) " after about " [i] " s, attempts=" (json select "tuto_as_p" "/nbattempt") [_n_] "activity log: " (json select "tuto_as_log" "/data/[0]/msg") " / " (json select "tuto_as_log" "/data/[1]/msg") " / " (json select "tuto_as_log" "/data/[2]/msg")); #4. Cleanup; json unload "tuto_as_p"; json unload "tuto_as_log"; stack delete_closed_id [pid]; sequence remove "tuto_as_invoice_done"; script delete "tuto.as.invoice.exe"; [out]
pid 1i pushed, state=W, attempts=0/1 closed at 2026-11-01 17:17:00 after about 35 s, attempts=1 activity log: [---- Begin script ----] / invoice 1001 gross 300 / [---- End script ----]
- Read the closed run later with
stack search "closed" "tuto.as.invoice.exe" .... - Use
stack_executewith a future date to run it tomorrow instead of now.
Plan reminders at J+7, J+15 and J+30, then cancel them
A dunning process queues three payment reminders in the future. The script generates the call template, queues the runs with stack_execute and stack, lists the queue, reads the variables of one run, and cancels them when the customer pays.
#1. The reminder script; script create exe "tuto.as.remind" false 1 (param (var "[customer]" {true} "Customer id" is_null:false is_empty:false "C042") (var "[step]" {true} "Reminder step" is_null:false is_empty:false "J+7") ;) "Send a payment reminder" { log write (concat "reminder " [step] " for " [customer]) "OK" "customer" [customer]; } "1"; #2. The call template, then three runs in the future; -> "[tpl]" (script generate stack "tuto.as.remind.exe"); -> "[w0]" (stack count_wait); -> "[p7]" (stack_execute (date dateaddt (date now) "DAY" 7) "tuto.as.remind.exe" "[customer]" "C042" "[step]" "J+7"); -> "[p15]" (stack (date dateaddt (date now) "DAY" 15) "tuto.as.remind.exe" "[customer]" "C042" "[step]" "J+15"); -> "[p30]" (stack (date dateaddt (date now) "DAY" 30) "tuto.as.remind.exe" "[customer]" "C042" "[step]" "J+30"); -> "[w1]" (stack count_wait); #3. The queue of this script, by execution date; json load "tuto_as_q" (string substring (stack search "wait" "tuto.as.remind.exe" "exe" (date now) (date dateaddt (date now) "DAY" 31) ASC 1 10) 39); -> "[list]" ""; for (-> "[i]" 0) (< [i] (json count "tuto_as_q" "/data")) (++ "[i]") { -> "[list]" (concat [list] (string substring (json select "tuto_as_q" (concat "/data/[" [i] "]/dtexe")) 0 10) " "); }; -> "[nq]" (json count "tuto_as_q" "/data"); -> "[vars]" (stack var_show [p15]); #4. The customer paid: cancel J+15 by id, then the rest of the script; stack delete_wait_id [p15]; -> "[left]" (stack delete_wait_script "tuto.as.remind.exe"); json unload "tuto_as_q"; script delete "tuto.as.remind.exe"; concat "waiting runs on the server: " [w0] " -> " [w1] ", this script: " [nq] " runs on " [list] [_n_] "variables of J+15: " [vars] [_n_] "J+15 cancelled, delete_wait_script removed " [left] " more" [_n_] "template:" [_n_] [tpl]
waiting runs on the server: 0 -> 3, this script: 3 runs on 2026-11-08 2026-11-16 2026-12-01
variables of J+15: [{"param":"[customer]","value":"C042"},{"param":"[step]","value":"J+15"}]
J+15 cancelled, delete_wait_script removed 2 more
template:
stack_execute "2026-11-01 17:18:29" "tuto.as.remind.exe"
"[customer]" "C042"
"[step]" "J+7"
;stack geton one pid shows its priority and the number of attempts.- Store the pids in a
tuto_node to cancel them from another script.
Priorities and parallel limit of the stack
Alerts must overtake heavy reports, and at most two reports may run at the same time. The script sets the stack options of both scripts, pushes three reports then two alerts due now, and looks at the queue during the cycle.
#1. A slow report and a fast alert; script create exe "tuto.as.report" false 1 (param (var "[n]" {type is_number [n]} "Report number" is_null:false is_empty:false "1") ;) "Build a heavy report" { wait "4000"; sequence increment "tuto_as_report_done"; } "1"; script create exe "tuto.as.alert" false 1 (param (var "[n]" {type is_number [n]} "Alert number" is_null:false is_empty:false "1") ;) "Send an alert" { sequence increment "tuto_as_alert_done"; } "1"; sequence add "tuto_as_report_done" 0; sequence add "tuto_as_alert_done" 0; #2. Alerts first with priority 9, at most 2 reports in parallel; script set stack "tuto.as.report.exe" 0 2 false; script set stack "tuto.as.alert.exe" 9 3 true; #3. Three reports, then two alerts, all due now; -> "[t0]" (date now); stack (date now) "tuto.as.report.exe" "[n]" 1; stack (date now) "tuto.as.report.exe" "[n]" 2; stack (date now) "tuto.as.report.exe" "[n]" 3; -> "[a1]" (stack (date now) "tuto.as.alert.exe" "[n]" 1); stack (date now) "tuto.as.alert.exe" "[n]" 2; json load "tuto_as_a" (stack get [a1]); -> "[opt]" (concat "alert entry: priority=" (json select "tuto_as_a" "/priority") ", nb_in_thread=" (json select "tuto_as_a" "/nb_in_thread")); #4. Wait for the cycle, then look while the reports run; -> "[i]" 0; while (and (< [i] 80) (< (sequence get current "tuto_as_alert_done") 2)) {wait "500"; ++ "[i]";}; wait "1000"; json load "tuto_as_r" (string substring (stack search "running" "tuto.as.report.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 10) 39); json load "tuto_as_w" (string substring (stack search "wait" "tuto.as.report.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 10) 39); -> "[mid]" (concat "1 s after the alerts: alerts done=" (sequence get current "tuto_as_alert_done") ", reports done=" (sequence get current "tuto_as_report_done") ", running=" (json count "tuto_as_r" "/data") ", waiting=" (json count "tuto_as_w" "/data")); wait "5000"; -> "[end]" (concat "5 s later: reports done=" (sequence get current "tuto_as_report_done") ", the third waits for the next cycle"); #5. Cleanup; -> "[del]" (concat "deleted: wait=" (stack delete_wait_script "tuto.as.report.exe") ", closed=" (+ (stack delete_closed_script "tuto.as.report.exe") (stack delete_closed_script "tuto.as.alert.exe"))); json unload "tuto_as_a"; json unload "tuto_as_r"; json unload "tuto_as_w"; sequence remove "tuto_as_report_done"; sequence remove "tuto_as_alert_done"; script delete "tuto.as.report.exe"; script delete "tuto.as.alert.exe"; concat [opt] [_n_] [mid] [_n_] [end] [_n_] [del]
alert entry: priority=9, nb_in_thread=3 1 s after the alerts: alerts done=2, reports done=0, running=2, waiting=1 5 s later: reports done=2, the third waits for the next cycle deleted: wait=1, closed=4
stack process_limitis the global ceiling of parallel runs on the node.- Replayable true on the alert allows
stack replay_error_idif it fails.
A run fails, the API comes back, replay it
A CRM synchronisation fails while the CRM is down. The script reads the error of the process, then once the CRM is back gives it one more attempt and replays that pid only.
#1. The worker fails while the CRM is down, its stack runs are replayable; script create exe "tuto.as.sync" false 1 (param (var "[customer]" {true} "Customer id" is_null:false is_empty:false "C042") ;) "Push a customer to the CRM" { if (== (sequence get current "tuto_as_crm_up") 0) {exception (1) ("CRM unreachable: HTTP 503");}; sequence increment "tuto_as_sync_done"; [customer]; } "The customer id"; script set stack "tuto.as.sync.exe" 0 1 true; sequence add "tuto_as_crm_up" 0; sequence add "tuto_as_sync_done" 0; #2. Push and wait until it lands in the error table; -> "[t0]" (date now); -> "[pid]" (stack (date now) "tuto.as.sync.exe" "[customer]" "C042"); -> "[i]" 0; -> "[n]" 0; while (and (< [i] 40) (< [n] 1)) { wait "1000"; ++ "[i]"; json load "tuto_as_e" (string substring (stack search "error" "tuto.as.sync.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 20) 39); -> "[n]" (json count "tuto_as_e" "/data"); }; json load "tuto_as_p" (stack get [pid]); -> "[err]" (concat "error after about " [i] " s, attempts " (json select "tuto_as_p" "/nbattempt") "/" (json select "tuto_as_p" "/nbmaxattempt") ": " (string regexp_extract (json select "tuto_as_p" "/lasterrormsg") "1: (.*)$" 1)); #3. The CRM is back: one more attempt, then replay this pid; sequence update "tuto_as_crm_up" 1; stack reset_error_nbattempt_id [pid]; -> "[rep]" (stack replay_error_id [pid]); -> "[i]" 0; while (and (< [i] 40) (== (sequence get current "tuto_as_sync_done") 0)) {wait "1000"; ++ "[i]";}; json load "tuto_as_p" (stack get [pid]); -> "[ok]" (concat "replayed=" [rep] ", closed at " (string substring (json select "tuto_as_p" "/dtclosed") 0 19) ", attempts=" (json select "tuto_as_p" "/nbattempt")); #4. Cleanup; stack delete_closed_id [pid]; json unload "tuto_as_p"; json unload "tuto_as_e"; sequence remove "tuto_as_crm_up"; sequence remove "tuto_as_sync_done"; script delete "tuto.as.sync.exe"; concat "pid " [pid] [_n_] [err] [_n_] [ok]
pid 2a error after about 1 s, attempts 1/1: CRM unreachable: HTTP 503 replayed=1, closed at 2026-11-01 17:19:00, attempts=1
- Give more attempts at creation time: the 4th argument of
script createis the number of attempts. stack replay_error_scriptreplays all the failed runs of a script at once.
Replay a whole batch of failed exports
Three nightly exports fail because the target share is offline. One file is known to be corrupt and is dropped; the others get one more attempt and are replayed by script once the share is back.
#1. The export fails while the share is offline; script create exe "tuto.as.export" false 1 (param (var "[file]" {true} "File to export" is_null:false is_empty:false "sales_A.csv") ;) "Export a file to the share" { if (== (sequence get current "tuto_as_share_up") 0) {exception (1) (concat "share offline, " [file] " not sent");}; sequence increment "tuto_as_export_done"; } "1"; script set stack "tuto.as.export.exe" 0 3 true; sequence add "tuto_as_share_up" 0; sequence add "tuto_as_export_done" 0; #2. Three exports, all fail at the next cycle; -> "[t0]" (date now); -> "[e0]" (stack count_error); stack (date now) "tuto.as.export.exe" "[file]" "sales_A.csv"; -> "[pb]" (stack (date now) "tuto.as.export.exe" "[file]" "sales_B.csv"); stack (date now) "tuto.as.export.exe" "[file]" "sales_C.csv"; -> "[i]" 0; -> "[n]" 0; while (and (< [i] 40) (< [n] 3)) { wait "1000"; ++ "[i]"; json load "tuto_as_e" (string substring (stack search "error" "tuto.as.export.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 20) 39); -> "[n]" (json count "tuto_as_e" "/data"); }; -> "[e1]" (stack count_error); #3. B is corrupt and dropped, the others get one more attempt and are replayed; stack delete_error_id [pb]; sequence update "tuto_as_share_up" 1; -> "[reset]" (stack reset_error_nbattempt_script "tuto.as.export.exe"); -> "[replayed]" (stack replay_error_script "tuto.as.export.exe"); -> "[i]" 0; while (and (< [i] 40) (< (sequence get current "tuto_as_export_done") 2)) {wait "1000"; ++ "[i]";}; #4. Cleanup; -> "[closed]" (stack delete_closed_script "tuto.as.export.exe"); json unload "tuto_as_e"; sequence remove "tuto_as_share_up"; sequence remove "tuto_as_export_done"; script delete "tuto.as.export.exe"; concat "errors on the server: " [e0] " -> " [e1] " (" [n] " of this script)" [_n_] "B dropped, reset=" [reset] ", replayed=" [replayed] [_n_] "closed after replay: " [closed] " (exports done in about " [i] " s)"
errors on the server: 0 -> 4 (3 of this script) B dropped, reset=2, replayed=OK: 2/2 closed after replay: 2 (exports done in about 30 s)
stack reset_error_nbattempt_idandstack replay_error_iddo the same for one pid.stack delete_error_scriptempties the error table of a script you gave up on.
Track the steps of a long import with a stack flow
An import runs in the stack and must report where it is: the worker names its flow, marks each step and keeps a JSON state; the caller reads the flow from the stack entry when it is closed.
#1. The worker names its flow, marks its steps and saves its state; script create exe "tuto.as.import" false 1 (param (var "[file]" {true} "File to import" is_null:false is_empty:false "orders_20261101.csv") ;) "Import a file in tracked steps" { stack flow_init [PID] "tuto_as_orders_import" "{}"; stack flow_step [PID] 1 "parse"; json load "tuto_as_flow" (stack flow_json_get [PID]); json iobject "tuto_as_flow" / "file" [file] STR; json iobject "tuto_as_flow" / "rows" 42 INT; stack flow_json_set [PID] (json doc "tuto_as_flow"); json unload "tuto_as_flow"; stack flow_step [PID] 2 "loaded"; sequence increment "tuto_as_import_done"; } "1"; sequence add "tuto_as_import_done" 0; #2. Push and wait; -> "[pid]" (stack (date now) "tuto.as.import.exe" "[file]" "orders_20261101.csv"); -> "[i]" 0; while (and (< [i] 40) (== (sequence get current "tuto_as_import_done") 0)) {wait "1000"; ++ "[i]";}; #3. The flow is stored in the stack entry; json load "tuto_as_p" (stack get [pid]); json load "tuto_as_s" (json select "tuto_as_p" "/json"); -> "[out]" (concat "flow " (json select "tuto_as_p" "/flowname") ", last step " (json select "tuto_as_p" "/pos") " = " (json select "tuto_as_p" "/posname") [_n_] "state: file=" (json select "tuto_as_s" "/file") ", rows=" (json select "tuto_as_s" "/rows")); #4. Cleanup; stack delete_closed_id [pid]; json unload "tuto_as_p"; json unload "tuto_as_s"; sequence remove "tuto_as_import_done"; script delete "tuto.as.import.exe"; [out]
flow tuto_as_orders_import, last step 2 = loaded state: file=orders_20261101.csv, rows=42
- Children pushed with
stackfrom the worker inherit the flow root: follow them withrestricted show_pid. - Read a running flow with
stack search "running" ...to build a progress bar.
Dashboard of the stack queue
Before a release, the developer checks the load of the stack and pages through the waiting runs of a script. The script reads the status, the limit and the four counters, queues three runs for tomorrow and pages through them two by two.
#1. Load of the stack; -> "[inuse]" (string regexp_extract (stack status) "NB_PROCESS_IN_STACK=([0-9]+)" 1); -> "[limit]" (stack process_limit); -> "[load]" (concat "threads " [inuse] "/" [limit] ", wait=" (stack count_wait) ", running=" (stack count_running) ", closed=" (stack count_closed) ", error=" (stack count_error)); #2. Three runs of a nightly script for tomorrow at 01:00, 01:10, 01:20; script create exe "tuto.as.nightly" false 1 (param (var "[part]" {true} "Partition to rebuild" is_null:false is_empty:false "P1") ;) "Rebuild one partition" { [part]; } "The partition"; -> "[day]" (date dateadd (date sysdate) "DAY" 1); stack (concat [day] " 01:00:00") "tuto.as.nightly.exe" "[part]" "P1"; stack (concat [day] " 01:10:00") "tuto.as.nightly.exe" "[part]" "P2"; stack (concat [day] " 01:20:00") "tuto.as.nightly.exe" "[part]" "P3"; #3. Page through them, 2 per page; -> "[pages]" ""; for (-> "[p]" 1) (<= [p] 2) (++ "[p]") { json load "tuto_as_pg" (string substring (stack search "wait" "tuto.as.nightly.exe" "exe" (concat [day] " 00:00:00") (concat [day] " 23:59:59") ASC [p] 2) 39); -> "[pages]" (concat [pages] "page " [p] ":"); for (-> "[i]" 0) (< [i] (json count "tuto_as_pg" "/data")) (++ "[i]") { -> "[pages]" (concat [pages] " " (string substring (json select "tuto_as_pg" (concat "/data/[" [i] "]/dtexe")) 11 16)); }; -> "[pages]" (concat [pages] [_n_]); }; #4. Cleanup; -> "[del]" (stack delete_wait_script "tuto.as.nightly.exe"); json unload "tuto_as_pg"; script delete "tuto.as.nightly.exe"; concat [load] [_n_] [pages] "test runs removed: " [del]
threads 0/10, wait=6, running=0, closed=0, error=0 page 1: 01:00 01:10 page 2: 01:20 test runs removed: 3
stack geton a pid of a page gives its variables and attempts.- Turn this into a
getweb service script to feed an admin screen.
A dispatcher pushes child runs and you follow the whole tree
A dispatcher run in the stack splits a job into three child runs. Each child knows its parent; the developer then reads the logs of the parent and of all its linked processes in one call.
#1. A child prices one region, the dispatcher pushes one child per region; script create exe "tuto.as.price_region" true 1 (param (var "[region]" {true} "Region code" is_null:false is_empty:false "NORTH") ;) "Recompute the prices of one region" { log write (concat "prices of " [region] " recomputed") "OK" "region" [region]; sequence increment "tuto_as_regions_done"; } "1"; script create exe "tuto.as.dispatch" true 1 (param ;) "Split the repricing by region" { -> "[regions]" "NORTH,SOUTH,WEST"; for (-> "[i]" 1) (<= [i] (atom size [regions] ",")) (++ "[i]") { stack (date now) "tuto.as.price_region.exe" "[region]" (atom get [regions] [i] ","); }; log write "3 regions dispatched" "OK" "regions" 3; } "1"; script set stack "tuto.as.price_region.exe" 0 3 false; sequence add "tuto_as_regions_done" 0; #2. Run the dispatcher through the stack: two cycles, parent then children; -> "[t0]" (date now); -> "[pid]" (stack (date now) "tuto.as.dispatch.exe"); -> "[i]" 0; while (and (< [i] 75) (< (sequence get current "tuto_as_regions_done") 3)) {wait "1000"; ++ "[i]";}; #3. A child knows its parent, show_pid gives the whole tree; json load "tuto_as_c" (string substring (stack search "closed" "tuto.as.price_region.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 10) 39); json load "tuto_as_c1" (stack get (atom get_last (json select "tuto_as_c" "/data/[0]/pid") " ")); json load "tuto_as_t" (string substring (restricted show_pid [pid]) 39); -> "[tree]" ""; for (-> "[k]" 0) (< [k] (json count "tuto_as_t" "/data")) (++ "[k]") { -> "[m]" (json select "tuto_as_t" (concat "/data/[" [k] "]/msg")); if (not (string starts_with [m] "[----")) {-> "[tree]" (concat [tree] " " (json select "tuto_as_t" (concat "/data/[" [k] "]/script")) ": " [m] [_n_]);}; }; -> "[out]" (concat "dispatcher pid " [pid] ", children closed: " (json count "tuto_as_c" "/data") " in about " [i] " s, parent_id of a child = " (json select "tuto_as_c1" "/parent_id") [_n_] "show_pid lines: " (json count "tuto_as_t" "/data") [_n_] [tree]); #4. Cleanup; stack delete_wait_script "tuto.as.price_region.exe"; stack delete_closed_script "tuto.as.price_region.exe"; stack delete_closed_script "tuto.as.dispatch.exe"; json unload "tuto_as_c"; json unload "tuto_as_c1"; json unload "tuto_as_t"; sequence remove "tuto_as_regions_done"; script delete "tuto.as.price_region.exe"; script delete "tuto.as.dispatch.exe"; [out]
dispatcher pid 2c, children closed: 3 in about 31 s, parent_id of a child = 2c show_pid lines: 12 tuto.as.dispatch.exe: 3 regions dispatched tuto.as.price_region.exe: prices of SOUTH recomputed tuto.as.price_region.exe: prices of WEST recomputed tuto.as.price_region.exe: prices of NORTH recomputed
- Inside a child,
[PPID]holds the pid of the dispatcher. log show_timegives the same lines for one pid only.
Clean the stack after a test campaign
After testing a worker, the developer leaves runs in every table: closed, in error and planned. The script creates that situation, counts the runs of the script per table, then empties each table for that script only and checks that nothing is left.
#1. A worker that fails on demand; script create exe "tuto.as.campaign" false 1 (param (var "[mode]" {true} "ok or fail" is_null:false is_empty:false "ok") ;) "Test worker of the campaign" { if (equal [mode] "fail") {exception (1) ("simulated failure");}; sequence increment "tuto_as_campaign_done"; } "1"; script set stack "tuto.as.campaign.exe" 0 4 false; sequence add "tuto_as_campaign_done" 0; #2. Two ok and one failing run now, two runs next week; -> "[t0]" (date now); stack (date now) "tuto.as.campaign.exe" "[mode]" "ok"; stack (date now) "tuto.as.campaign.exe" "[mode]" "ok"; stack (date now) "tuto.as.campaign.exe" "[mode]" "fail"; stack (date dateaddt (date now) "DAY" 7) "tuto.as.campaign.exe" "[mode]" "ok"; stack (date dateaddt (date now) "DAY" 8) "tuto.as.campaign.exe" "[mode]" "ok"; -> "[i]" 0; -> "[n]" 0; while (and (< [i] 40) (< [n] 1)) { wait "1000"; ++ "[i]"; json load "tuto_as_e" (string substring (stack search "error" "tuto.as.campaign.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 20) 39); -> "[n]" (json count "tuto_as_e" "/data"); }; while (and (< [i] 45) (< (sequence get current "tuto_as_campaign_done") 2)) {wait "1000"; ++ "[i]";}; #3. Runs of this script per table; -> "[tables]" "wait,closed,error"; -> "[before]" ""; for (-> "[k]" 1) (<= [k] 3) (++ "[k]") { -> "[tb]" (atom get [tables] [k] ","); json load "tuto_as_q" (string substring (stack search [tb] "tuto.as.campaign.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 20) 39); -> "[before]" (concat [before] [tb] "=" (json count "tuto_as_q" "/data") " "); }; #4. Empty each table for this script only; -> "[del]" (concat "deleted wait=" (stack delete_wait_script "tuto.as.campaign.exe") " closed=" (stack delete_closed_script "tuto.as.campaign.exe") " error=" (stack delete_error_script "tuto.as.campaign.exe")); -> "[after]" ""; for (-> "[k]" 1) (<= [k] 3) (++ "[k]") { -> "[tb]" (atom get [tables] [k] ","); json load "tuto_as_q" (string substring (stack search [tb] "tuto.as.campaign.exe" "create" [t0] (date dateaddt (date now) "HOUR" 1) ASC 1 20) 39); -> "[after]" (concat [after] [tb] "=" (json count "tuto_as_q" "/data") " "); }; json unload "tuto_as_q"; json unload "tuto_as_e"; sequence remove "tuto_as_campaign_done"; script delete "tuto.as.campaign.exe"; concat "before: " [before] [_n_] [del] [_n_] "after: " [after]
before: wait=2 closed=2 error=1 deleted wait=2 closed=2 error=1 after: wait=0 closed=0 error=0
- Delete one run only with
stack delete_wait_id,stack delete_closed_idorstack delete_error_id. - The server also purges closed and error runs every night with a retention (jobs server.stack.remove_*).
Same script: execute now or hand it to the stack
A VAT script is called synchronously by a web service and asynchronously by a batch. The script generates both call templates, runs it with execute and call, pushes it to the stack, and compares what the caller gets back in each case.
#1. A VAT script that also keeps the last gross amount in a sequence; script create exe "tuto.as.vat" false 1 (param (var "[net]" {type is_number [net]} "Net amount in cents" is_null:false is_empty:false "10000") ;) "Gross amount in cents with 20 percent VAT" { -> "[gross]" (/ (* [net] 120) 100); sequence update "tuto_as_vat_last" [gross]; [gross]; } "The gross amount in cents"; sequence add "tuto_as_vat_last" 0; -> "[gen]" (concat (script generate execute "tuto.as.vat.exe") [_n_] (script generate stack "tuto.as.vat.exe")); #2. Synchronous: the caller waits and gets the value; -> "[r1]" (execute "tuto.as.vat.exe" "[net]" 10000); -> "[r2]" (call "tuto.as.vat.exe" "[net]" 2500); #3. Asynchronous: the caller only gets a pid; -> "[pid]" (stack (date now) "tuto.as.vat.exe" "[net]" 99900); -> "[i]" 0; while (and (< [i] 40) (not (equal (sequence get current "tuto_as_vat_last") "119880"))) {wait "1000"; ++ "[i]";}; -> "[r3]" (sequence get current "tuto_as_vat_last"); #4. Cleanup; stack delete_closed_id [pid]; sequence remove "tuto_as_vat_last"; script delete "tuto.as.vat.exe"; concat "execute -> " [r1] ", call -> " [r2] [_n_] "stack -> pid " [pid] ", result read " [i] " s later in the sequence: " [r3] [_n_] "templates:" [_n_] [gen]
execute -> 12000, call -> 3000 stack -> pid 4o, result read 16 s later in the sequence: 119880 templates: execute "tuto.as.vat.exe" "[net]" "10000" ; stack_execute "2026-11-01 17:28:14" "tuto.as.vat.exe" "[net]" "10000" ;
includeruns the script inline: its variables stay in the caller after the call.- A stack run returns nothing to the caller: write its result in a node, a sequence or a table.
