Project

General

Profile

issue_25.txt

Sergey Ivanovskiy, 05/22/2026 07:39 AM

Download (2.72 KB)

 
1
ISSUE DESCRIPTION:
2
*[MAJOR]* _performance_ @ModalDialog@.@operationGuard@: Every dialog-mutation method does @operationGuard = operationGuard.then(async () => ...)@. The chain is never pruned; each call (even no-op early-returns) permanently extends the microtask tail. Only freed when @close()@ discards the dialog. For hot callers (e.g. @actionResultsDialog.show()@ in every uploader @onProgress@ event in @chooseFiles@) this builds an unbounded queue during uploads.
3
=> Fix: After each mutation method's awaited body completes, replace operationGuard with Promise.resolve() — i.e. start a fresh chain (or reset to Promise.resolve() whenever the chain is empty). Alternative: skip queueing when operationGuard is already-resolved.
4

    
5
PROPOSED FIX:
6
Track an inflight counter; once the last queued mutation settles, reset operationGuard to Promise.resolve(). This collapses the chain at idle and prevents an unbounded microtask tail under hot callers like the uploader progress events. Promise behavior for in-flight awaiters is unchanged because they already hold the older promise reference.
7

    
8
CHANGES:
9
/home/sbi/projects/10915a/src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js
10

    
11
Before (lines ~2287, plus the typical mutation method pattern):
12
      /** The last asynchronous operation */
13
      var operationGuard = Promise.resolve();
14

    
15
      ...
16

    
17
      async function show()
18
      {
19
         operationGuard = operationGuard.then(async () =>
20
         {
21
            ...
22
         });
23
         return operationGuard;
24
      }
25

    
26
After:
27
      /** The last asynchronous operation */
28
      var operationGuard = Promise.resolve();
29
      /** Inflight queued mutation count; when 0 the chain may be reset. */
30
      var operationInflight = 0;
31

    
32
      /**
33
       * Append a mutation step to the operationGuard chain. When the queue
34
       * drains, reset operationGuard to Promise.resolve() so the chain does
35
       * not grow unboundedly under hot callers (e.g. uploader progress).
36
       */
37
      function enqueueOperation(stepFn)
38
      {
39
         operationInflight++;
40
         var next = operationGuard.then(stepFn).finally(function()
41
         {
42
            operationInflight--;
43
            if (operationInflight === 0)
44
            {
45
               operationGuard = Promise.resolve();
46
            }
47
         });
48
         operationGuard = next;
49
         return next;
50
      }
51

    
52
      ...
53

    
54
      async function show()
55
      {
56
         return enqueueOperation(async () =>
57
         {
58
            ...
59
         });
60
      }
61

    
62
(Apply the same enqueueOperation(...) wrapper to hide, close, setContent,
63
setTextContent, setButtons, setFooter, setTitle, overrideDialogStyle,
64
overrideTitleStyle, updateLayout — every method that currently does
65
`operationGuard = operationGuard.then(async () => { ... })`.)