Parsware
All articles

Parsware Platform

Rules with limits

A loop that never ends is a typo away, and on the server it would hold a save open forever. So every script runs on a budget. We wrote two broken plugin steps, a loop counting the wrong way and a function with no way out, and read what the platform says when it stops them.

A new order line refused with "Plugin step Count the pallets failed: The script was stopped after 5000ms (line 3): it is taking longer than this surface allows.", next to the title "Rules with limits"

Everyone writes a loop that doesn't end eventually. i-- where you meant i++, a condition that's never quite met, a function that calls itself and forgets to stop.

In a form rule, that would freeze the browser tab, with somebody's unsaved record in it. In a plugin step it's worse: the step runs inside the save, so the save would never finish, and the record being written would stay locked for everybody else.

So no script in Parsware can run forever. Each one runs on a budget, and when it runs out the script is stopped with a sentence that says where. This article breaks a plugin step twice to see those sentences. We used the order lines from the Order Stock sample, but any table will do.

A loop that counts the wrong way

In Main we added a step, Count the pallets, on Order line, Create, Before the write. It was meant to work out how many pallets of 12 a line fills. It has a typo:

The step's code: a comment, var pallets = 0, then for (var i: int = 0; i < 12; i--) adding one to pallets

// How many pallets of 12 this line fills. (There is a typo in the loop.)
var pallets = 0;
for (var i: int = 0; i < 12; i--) {
  pallets = pallets + 1;
}

i-- counts down from 0, so i < 12 is always true. The editor accepts it: it's a perfectly good loop, just not the one we meant. We saved an order line in the Shell:

A new order line, Pallet check, refused with "Plugin step 'Count the pallets' failed: The script was stopped after 5000ms (line 3): it is taking longer than this surface allows."

About five seconds later the save was refused: The script was stopped after 5000ms (line 3). The message names the step and the line the script was on when it was stopped, which is the loop. Nothing was stored, because the step runs before the write.

A function with no way out

We tried again with recursion: a function that takes 12 off and calls itself, but has no check for when to stop:

The step's code: func pallets(left: number) returning 1 + pallets(left - 12), with no stop condition, and var needed = pallets(30)

// How many pallets of 12 this line fills, one pallet at a time. (The stop condition is missing.)
func pallets(left: number): number {
  return 1 + pallets(left - 12);
}

var needed = pallets(30);

A new order line refused with "Plugin step 'Count the pallets' failed: The script was stopped 200 calls deep (line 3): a function is calling itself without ever finishing."

This one was stopped much faster, and says why: 200 calls deep … a function is calling itself without ever finishing. Adding the missing check fixes it:

The corrected code: pallets returns 0 when left is 0 or less, and 1 + pallets(left - 12) otherwise

func pallets(left: number): number {
  if (left <= 0) {
    return 0;
  }
  return 1 + pallets(left - 12);
}

With that, the order line saved straight away.

Three limits

A script is stopped when it passes any one of three limits:

Limit What it catches What the message says
Steps A loop that never ends. Every statement, loop turn and call counts as a step stopped after N steps
Time A script that isn't looping but is too slow stopped after N ms
Depth A function calling itself without end stopped N calls deep

The loop above could have hit either the step or the time limit; on our machine the clock ran out first. Either way the message gives the line.

A try/catch in the script can't catch these. A limit a script could switch off by wrapping the loop in try wouldn't be a limit.

Different places, different budgets

The limits depend on where the script runs, because the cost of a slow script depends on who's waiting for it:

Where the script runs Steps Time Depth
A form rule, in the browser 100,000 0.5 s 100
A command button, in the browser 500,000 2 s 100
A data policy's check 200,000 1 s 100
A plugin step or a Custom API 2,000,000 5 s 200
A scheduled job 50,000,000 2 min 200

A form rule has the tightest budget because it runs between one keystroke and the next, and the page can't do anything else meanwhile. A scheduled job has the longest because nobody is waiting and it may walk thousands of rows. None is unlimited: a nightly job that never finished would never start again the next night.

These are generous for real work. The largest rule in any sample uses about a thousand steps. If a script you meant to write hits one, the loop or the recursion is almost always the reason, not the limit.

Fixes from writing this

The first time we saw the loop's message, it read …than this surface allows.., with two full stops, and named the step new_line_count_pallets, its logical name. It now names the step as you called it, with one full stop. The step limit's message also used to say "than a form script should", even for a plugin step on the server. It now says "than this surface allows", in both the browser and the server.

The recursion example found a real difference between the two. The editor and the browser accept pallets(30) into a parameter declared number, because a whole number is a number. The server refused it with Function overload for pallets not found, so the script that looked fine in the editor failed on the first save. The server now accepts it too, and the test suite both interpreters must pass holds them to it. A decimal into a parameter declared int is still refused, on both sides.

Try it

Write the classic, while (true) { }, in a plugin step, save a record and read the message. Then make the loop do something slow but finite, such as counting to a million, and see whether it finishes inside the budget or not.

Next: environment variables: values that change per environment, read by your server scripts.