Button click takes too long / transaction timeout: run the heavy part in the background

seungchulkim
Tera Contributor

Hi I am happy to share my Lesson Learned Here,

You can achieve this by letting the button (UI Action or GlideAjax) only mark what needs processing and start an On Demand Scheduled Job. The job picks up the marked records and finishes in the background, so the user gets a response in about a second.

 

Step 1: Create an On Demand Scheduled Job

  1. Navigate to System Definition → Scheduled Jobs → New → Automatically run a script of your choosing.
  2. Name: My Reapply Job, Run: On Demand.
  3. Script:
    new MyProcessor().drain();

    Step 2: Add a drain() method that only processes marked records


    drain: function () {
        var g = new GlideRecord('u_my_rule'), ids = [], keys = [];
        g.addQuery('u_review_reason', 'PENDING');
        g.query();
        while (g.next()) { ids.push(g.getUniqueValue()); keys.push(g.getValue('u_pattern')); }
        if (!ids.length) return { rows: 0 };                 // safe to run any number of times
    
        var rows = this._process(keys);                      // the heavy work
    
        var u = new GlideRecord('u_my_rule');
        u.addQuery('sys_id', 'IN', ids.join(','));           // clear only what we picked up
        u.setWorkflow(false); u.query();
        u.setValue('u_review_reason', '');
        u.updateMultiple();
        return { rows: rows };
    }

    Step 3: In the GlideAjax / UI Action, process small requests now and queue large ones

    var max = parseInt(gs.getProperty('x.apply_now_max', '20000'), 10);
    if (affected <= max) {
        out.rows = this._process(keys);  out.mode = 'now';
    } else {
        ids.forEach(function (id) {
            var g = new GlideRecord('u_my_rule');
            if (g.get(id)) { g.setValue('u_review_reason', 'PENDING'); g.setWorkflow(false); g.update(); }
        });
        var job = new GlideRecord('sysauto_script');
        if (job.get('name', 'My Reapply Job')) {
            SncTriggerSynchronizer.executeNow(job);         // starts the On Demand job right away
            out.mode = 'queued';
        } else {
            out.mode = 'batch';                              // falls back to the nightly job
        }
    }
    return JSON.stringify(out);

    Step 4: Show the user what happened

    if (o.mode === 'queued') g_form.addInfoMessage('Re-processing started in the background.');
    else if (o.mode === 'now') g_form.addInfoMessage(o.rows + ' records updated.');

    Why the PENDING flag matters: if the job fails or someone deletes it, the flag stays and the next scheduled run still picks the records up. Nothing is lost.

    If this helped, please mark it as Helpful

0 REPLIES 0