@@ -226,6 +226,92 @@ test.serial('kill: oom (large, kill vm)', async (t) => {
226226 t . is ( error_message , 'Run exceeded maximum memory usage' ) ;
227227} ) ;
228228
229+ test . serial ( 'kill: state exceeds the configured state limit' , async ( t ) => {
230+ const attempt = {
231+ id : crypto . randomUUID ( ) ,
232+ jobs : [
233+ {
234+ adaptor : '@openfn/language-common@latest' ,
235+ // ~2mb string for a 1mb limit
236+ body : `fn((s) => {
237+ s.data = new Array(2 * 1024 * 1024).fill('a').join('');
238+ return s;
239+ })` ,
240+ } ,
241+ ] ,
242+ options : {
243+ state_limit_mb : 1 ,
244+ } ,
245+ } ;
246+
247+ const result = await run ( attempt ) ;
248+
249+ const { reason, error_type, error_message } = result ;
250+ t . is ( reason , 'kill' ) ;
251+ t . is ( error_type , 'StateTooLargeError' ) ;
252+ t . regex ( error_message , / S t a t e e x c e e d s t h e l i m i t o f 1 m b / ) ;
253+ } ) ;
254+
255+ test . serial (
256+ 'kill: state limit is enforced between jobs (downstream job does not run)' ,
257+ async ( t ) => {
258+ const jobOne = {
259+ id : crypto . randomUUID ( ) ,
260+ adaptor : '@openfn/language-common@latest' ,
261+ // ~2mb state, over the 1mb limit set below
262+ body : `fn((s) => {
263+ s.data = new Array(2 * 1024 * 1024).fill('a').join('');
264+ return s;
265+ })` ,
266+ } ;
267+
268+ // not expected to run because the first job is expected to trigger state size crash
269+ const jobTwo = {
270+ id : crypto . randomUUID ( ) ,
271+ adaptor : '@openfn/language-common@latest' ,
272+ body : `fn(() => ({ data: 'ok' }))` ,
273+ } ;
274+
275+ const attempt = {
276+ id : crypto . randomUUID ( ) ,
277+ jobs : [ jobOne , jobTwo ] ,
278+ edges : [
279+ {
280+ id : crypto . randomUUID ( ) ,
281+ source_job_id : jobOne . id ,
282+ target_job_id : jobTwo . id ,
283+ condition : 'always' ,
284+ } ,
285+ ] ,
286+ options : {
287+ state_limit_mb : 1 ,
288+ } ,
289+ } ;
290+
291+ const startedJobs : string [ ] = [ ] ;
292+ const unsubscribe = lightning . onSocketEvent (
293+ 'step:start' ,
294+ attempt . id ,
295+ ( evt ) => {
296+ if ( evt . runId === attempt . id ) {
297+ startedJobs . push ( evt . payload . job_id ) ;
298+ }
299+ } ,
300+ false
301+ ) ;
302+
303+ const result = await run ( attempt ) ;
304+ unsubscribe ( ) ;
305+
306+ const { reason, error_type, error_message } = result ;
307+ t . is ( reason , 'kill' ) ;
308+ t . is ( error_type , 'StateTooLargeError' ) ;
309+ t . regex ( error_message , / S t a t e e x c e e d s t h e l i m i t o f 1 m b / ) ;
310+
311+ t . deepEqual ( startedJobs , [ jobOne . id ] ) ;
312+ }
313+ ) ;
314+
229315test . serial ( 'crash: process.exit() triggered by postgres' , async ( t ) => {
230316 const attempt = {
231317 id : crypto . randomUUID ( ) ,
0 commit comments