• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #13107

27 Sep 2024 07:17PM UTC coverage: 88.98% (+0.8%) from 88.193%
#13107

push

web-flow
Merge 8a202434b into 3a2dc7282

7180 of 8502 branches covered (84.45%)

Branch coverage included in aggregate %.

1025 of 1156 new or added lines in 28 files covered. (88.67%)

24 existing lines in 5 files now uncovered.

9582 of 10336 relevant lines covered (92.71%)

1783.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.35
/src/BusyStatusTracker.ts
1
import { EventEmitter } from 'eventemitter3';
1✔
2
import { Deferred } from './deferred';
1✔
3

4
/**
5
 * Tracks the busy/idle status of various sync or async tasks
6
 * Reports the overall status to the client
7
 */
8
export class BusyStatusTracker {
1✔
9
    /**
10
     * @readonly
11
     */
12
    public activeRuns = new Set<{
124✔
13
        label?: string;
14
        startTime?: Date;
15
    }>();
16

17
    /**
18
     * Collection to track scoped runs. All runs represented here also have a corresponding entry in `activeRuns`
19
     */
20
    private scopedRuns = new Map<any, Array<{
124✔
21
        label: string;
22
        deferred: Deferred;
23
    }>>();
24

25
    /**
26
     * Begin a busy task. It's expected you will call `endScopeRun` when the task is complete.
27
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
28
     * @param label  label for the run. This is required for the `endScopedRun` method to know what to end.
29
     */
30
    public beginScopedRun(scope: any, label: string) {
31
        let runsForScope = this.scopedRuns.get(scope);
86✔
32
        if (!runsForScope) {
86✔
33
            runsForScope = [];
68✔
34
            this.scopedRuns.set(scope, runsForScope);
68✔
35
        }
36

37
        const deferred = new Deferred();
86✔
38

39
        void this.run(() => {
86✔
40
            //don't mark the busy run as completed until the deferred is resolved
41
            return deferred.promise;
86✔
42
        }, label);
43

44
        runsForScope.push({
86✔
45
            label: label,
46
            deferred: deferred
47
        });
48
    }
49

50
    /**
51
     * End the earliest run for the given scope and label
52
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
53
     * @param label label for the run
54
     */
55
    public endScopedRun(scope: any, label: string) {
56
        const runsForScope = this.scopedRuns.get(scope);
80✔
57
        if (!runsForScope) {
80!
NEW
58
            return;
×
59
        }
60
        const earliestRunIndex = runsForScope.findIndex(x => x.label === label);
80✔
61
        if (earliestRunIndex === -1) {
80!
NEW
62
            return;
×
63
        }
64
        const earliestRun = runsForScope[earliestRunIndex];
80✔
65
        runsForScope.splice(earliestRunIndex, 1);
80✔
66
        earliestRun.deferred.resolve();
80✔
67
        return earliestRun.deferred.promise;
80✔
68
    }
69

70
    /**
71
     * End all runs for a given scope. This is typically used when the scope is destroyed, and we want to make sure all runs are cleaned up.
72
     * @param scope an object used for reference as to what is doing the work.
73
     */
74
    public endAllRunsForScope(scope: any) {
75
        const runsForScope = this.scopedRuns.get(scope);
12✔
76
        if (!runsForScope) {
12✔
77
            return;
3✔
78
        }
79
        for (const run of runsForScope) {
9✔
80
            run.deferred.resolve();
3✔
81
        }
82
        this.scopedRuns.delete(scope);
9✔
83
    }
84

85
    /**
86
     * Start a new piece of work
87
     */
88
    public run<T, R = T | Promise<T>>(callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
89
        const run = {
354✔
90
            label: label,
91
            startTime: new Date()
92
        };
93
        this.activeRuns.add(run);
354✔
94

95
        if (this.activeRuns.size === 1) {
354✔
96
            this.emit('change', BusyStatus.busy);
200✔
97
        }
98

99
        let isFinalized = false;
354✔
100
        const finalizeRun = () => {
354✔
101
            if (isFinalized === false) {
353✔
102
                isFinalized = true;
351✔
103
                this.activeRuns.delete(run);
351✔
104
                if (this.activeRuns.size <= 0) {
351✔
105
                    this.emit('change', BusyStatus.idle);
199✔
106
                }
107
            }
108
        };
109

110
        let result: R | PromiseLike<R>;
111
        //call the callback function
112
        try {
354✔
113
            result = callback(finalizeRun);
354✔
114
            //if the result is a promise, don't finalize until it completes
115
            if (typeof (result as any)?.then === 'function') {
353✔
116
                return Promise.resolve(result).finally(finalizeRun).then(() => result) as any;
344✔
117
            } else {
118
                finalizeRun();
9✔
119
                return result;
9✔
120
            }
121
        } catch (e) {
122
            finalizeRun();
1✔
123
            throw e;
1✔
124
        }
125
    }
126

127
    private emitter = new EventEmitter<string, BusyStatus>();
124✔
128

129
    public once(eventName: 'change'): Promise<BusyStatus>;
130
    public once<T>(eventName: string): Promise<T> {
131
        return new Promise<T>((resolve) => {
4✔
132
            const off = this.on(eventName as any, (data) => {
4✔
133
                off();
4✔
134
                resolve(data as any);
4✔
135
            });
136
        });
137
    }
138

139
    public on(eventName: 'change', handler: (status: BusyStatus) => void) {
140
        this.emitter.on(eventName, handler);
58✔
141
        return () => {
58✔
142
            this.emitter.off(eventName, handler);
5✔
143
        };
144
    }
145

146
    private emit(eventName: 'change', value: BusyStatus) {
147
        this.emitter.emit(eventName, value);
399✔
148
    }
149

150
    public destroy() {
151
        this.emitter.removeAllListeners();
12✔
152
    }
153

154
    /**
155
     * The current status of the busy tracker.
156
     * @readonly
157
     */
158
    public get status() {
159
        return this.activeRuns.size === 0 ? BusyStatus.idle : BusyStatus.busy;
228✔
160
    }
161
}
162

163
export type FinalizeBuildStatusRun = (status?: BusyStatus) => void;
164

165
export enum BusyStatus {
1✔
166
    busy = 'busy',
1✔
167
    idle = 'idle'
1✔
168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc