Skip to content

Commit 76f9034

Browse files
author
Beatriz Rizental
authored
Merge pull request #692 from brizental/1728128-restarted-webext-qt-verify
Bug 1728128 - Ensure events database is initialized at a time when metrics can be recorded
2 parents fca1265 + 0c487b9 commit 76f9034

File tree

9 files changed

+175
-96
lines changed

9 files changed

+175
-96
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
* [#661](https://github.com/mozilla/glean.js/pull/661): Include unminified version of library on Qt/QML builds.
1414
* [#647](https://github.com/mozilla/glean.js/pull/647): Implement the Text metric type.
1515
* [#681](https://github.com/mozilla/glean.js/pull/681): BUGFIX: Fix error in scanning events database upon initialization on Qt/QML.
16-
* This bug prevents the changes introduced in [#526](https://github.com/mozilla/glean.js/pull/526) from working properly.
17-
* [#614](https://github.com/mozilla/glean.js/pull/614): Implement the String List metric type.
16+
* This bug prevents the changes introduced in [#526](https://github.com/mozilla/glean.js/pull/526) from working properly in Qt/QML.
17+
* [#692](https://github.com/mozilla/glean.js/pull/692): BUGFIX: Ensure events database is initialized at a time Glean is already able to record metrics.
18+
* This bug also prevents the changes introduced in [#526](https://github.com/mozilla/glean.js/pull/526) from working properly in all platforms.
1819

1920
# v0.18.1 (2021-07-22)
2021

glean/src/core/context.ts

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type EventsDatabase from "./metrics/events_database/index.js";
88
import type PingsDatabase from "./pings/database.js";
99
import type ErrorManager from "./error/index.js";
1010
import Dispatcher from "./dispatcher.js";
11+
import log, { LoggingLevel } from "./log.js";
12+
13+
const LOG_TAG = "core.Context";
1114

1215
/**
1316
* This class holds all of the Glean singleton's state and internal dependencies.
@@ -22,30 +25,29 @@ import Dispatcher from "./dispatcher.js";
2225
* which only matter for Typescript and don't cause circular dependency issues.
2326
*/
2427
export class Context {
25-
private static _instance: Context;
28+
private static _instance?: Context;
2629

27-
private _dispatcher!: Dispatcher | null;
30+
private _dispatcher: Dispatcher;
2831

32+
// The following group of properties are all set on Glean.initialize
33+
// Attempting to get them before they are set will log an error.
2934
private _uploadEnabled!: boolean;
3035
private _metricsDatabase!: MetricsDatabase;
3136
private _eventsDatabase!: EventsDatabase;
3237
private _pingsDatabase!: PingsDatabase;
33-
// The reason this was added to the Context,
34-
// is to avoid a circular dependency between
35-
// the ErrorManager's module and the CounterMetricType module.
3638
private _errorManager!: ErrorManager;
37-
3839
private _applicationId!: string;
39-
private _initialized: boolean;
40-
4140
private _debugOptions!: DebugOptions;
4241

42+
private _initialized: boolean;
43+
4344
// The moment the current Glean.js session started.
4445
private _startTime: Date;
4546

4647
private constructor() {
4748
this._initialized = false;
4849
this._startTime = new Date();
50+
this._dispatcher = new Dispatcher();
4951
}
5052

5153
static get instance(): Context {
@@ -61,39 +63,25 @@ export class Context {
6163
*
6264
* Resets the Context to an uninitialized state.
6365
*/
64-
static async testUninitialize(): Promise<void> {
65-
// Clear the dispatcher queue
66-
// and return the dispatcher back to an uninitialized state.
67-
if (Context.instance._dispatcher) {
68-
await Context.instance._dispatcher.testUninitialize();
69-
}
70-
71-
// Due to the test requirement of keeping storage in place,
72-
// we can't simply wipe out the full `Context` instance.
73-
// The closest thing we can do is making the dispatcher `null`.
74-
Context.instance._dispatcher = null;
75-
76-
Context.initialized = false;
77-
Context._instance._startTime = new Date();
66+
static testUninitialize(): void {
67+
Context._instance = undefined;
7868
}
7969

8070
static get dispatcher(): Dispatcher {
81-
// Create a dispatcher if one isn't available already.
82-
// This is required since the dispatcher may be used
83-
// earlier than Glean initialization, so we can't rely
84-
// on `Glean.initialize` to set it.
85-
if (!Context.instance._dispatcher) {
86-
Context.instance._dispatcher = new Dispatcher();
87-
}
88-
8971
return Context.instance._dispatcher;
9072
}
9173

92-
static set dispatcher(dispatcher: Dispatcher) {
93-
Context.instance._dispatcher = dispatcher;
94-
}
95-
9674
static get uploadEnabled(): boolean {
75+
if (typeof Context.instance._uploadEnabled === "undefined") {
76+
log(
77+
LOG_TAG,
78+
[
79+
"Attempted to access Context.uploadEnabled before it was set. This may cause unexpected behaviour.",
80+
],
81+
LoggingLevel.Error
82+
);
83+
}
84+
9785
return Context.instance._uploadEnabled;
9886
}
9987

@@ -102,6 +90,16 @@ export class Context {
10290
}
10391

10492
static get metricsDatabase(): MetricsDatabase {
93+
if (typeof Context.instance._metricsDatabase === "undefined") {
94+
log(
95+
LOG_TAG,
96+
[
97+
"Attempted to access Context.metricsDatabase before it was set. This may cause unexpected behaviour.",
98+
],
99+
LoggingLevel.Error
100+
);
101+
}
102+
105103
return Context.instance._metricsDatabase;
106104
}
107105

@@ -110,6 +108,16 @@ export class Context {
110108
}
111109

112110
static get eventsDatabase(): EventsDatabase {
111+
if (typeof Context.instance._eventsDatabase === "undefined") {
112+
log(
113+
LOG_TAG,
114+
[
115+
"Attempted to access Context.eventsDatabase before it was set. This may cause unexpected behaviour.",
116+
],
117+
LoggingLevel.Error
118+
);
119+
}
120+
113121
return Context.instance._eventsDatabase;
114122
}
115123

@@ -118,6 +126,16 @@ export class Context {
118126
}
119127

120128
static get pingsDatabase(): PingsDatabase {
129+
if (typeof Context.instance._pingsDatabase === "undefined") {
130+
log(
131+
LOG_TAG,
132+
[
133+
"Attempted to access Context.pingsDatabase before it was set. This may cause unexpected behaviour.",
134+
],
135+
LoggingLevel.Error
136+
);
137+
}
138+
121139
return Context.instance._pingsDatabase;
122140
}
123141

@@ -126,6 +144,16 @@ export class Context {
126144
}
127145

128146
static get errorManager(): ErrorManager {
147+
if (typeof Context.instance._errorManager === "undefined") {
148+
log(
149+
LOG_TAG,
150+
[
151+
"Attempted to access Context.errorManager before it was set. This may cause unexpected behaviour.",
152+
],
153+
LoggingLevel.Error
154+
);
155+
}
156+
129157
return Context.instance._errorManager;
130158
}
131159

@@ -134,6 +162,16 @@ export class Context {
134162
}
135163

136164
static get applicationId(): string {
165+
if (typeof Context.instance._applicationId === "undefined") {
166+
log(
167+
LOG_TAG,
168+
[
169+
"Attempted to access Context.applicationId before it was set. This may cause unexpected behaviour.",
170+
],
171+
LoggingLevel.Error
172+
);
173+
}
174+
137175
return Context.instance._applicationId;
138176
}
139177

@@ -150,6 +188,16 @@ export class Context {
150188
}
151189

152190
static get debugOptions(): DebugOptions {
191+
if (typeof Context.instance._debugOptions === "undefined") {
192+
log(
193+
LOG_TAG,
194+
[
195+
"Attempted to access Context.debugOptions before it was set. This may cause unexpected behaviour.",
196+
],
197+
LoggingLevel.Error
198+
);
199+
}
200+
153201
return Context.instance._debugOptions;
154202
}
155203

@@ -158,6 +206,6 @@ export class Context {
158206
}
159207

160208
static get startTime(): Date {
161-
return Context._instance._startTime;
209+
return Context.instance._startTime;
162210
}
163211
}

glean/src/core/glean.ts

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,13 @@ class Glean {
9999
* Afterward, the upload_enabled flag is set to false.
100100
*/
101101
private static async onUploadDisabled(): Promise<void> {
102+
// It's fine to set this before submitting the deletion request ping,
103+
// that ping is still sent even if upload is disabled.
104+
Context.uploadEnabled = false;
102105
// We need to use an undispatched submission to guarantee that the
103106
// ping is collected before metric are cleared, otherwise we end up
104107
// with malformed pings.
105108
await PingType._private_submitUndispatched(Glean.corePings.deletionRequest);
106-
107-
Context.uploadEnabled = false;
108109
await Glean.clearMetrics();
109110
}
110111

@@ -210,15 +211,19 @@ class Glean {
210211
return;
211212
}
212213

214+
Context.applicationId = sanitizeApplicationId(applicationId);
215+
213216
// The configuration constructor will throw in case config has any incorrect prop.
214217
const correctConfig = new Configuration(config);
218+
Context.debugOptions = correctConfig.debug;
219+
Glean.instance._config = correctConfig;
215220

216221
Context.metricsDatabase = new MetricsDatabase(Glean.platform.Storage);
217222
Context.eventsDatabase = new EventsDatabase(Glean.platform.Storage);
218223
Context.pingsDatabase = new PingsDatabase(Glean.platform.Storage);
219224
Context.errorManager = new ErrorManager();
220225

221-
Glean.instance._pingUploader = new PingUploader(correctConfig, Glean.platform);
226+
Glean.instance._pingUploader = new PingUploader(correctConfig, Glean.platform, Context.pingsDatabase);
222227

223228
Context.pingsDatabase.attachObserver(Glean.pingUploader);
224229

@@ -235,26 +240,19 @@ class Glean {
235240
//
236241
// The dispatcher will catch and log any exceptions.
237242
Context.dispatcher.flushInit(async () => {
238-
Context.applicationId = sanitizeApplicationId(applicationId);
239-
Context.debugOptions = correctConfig.debug;
240-
Glean.instance._config = correctConfig;
241-
242-
243-
// IMPORTANT!
244-
// Any pings we want to send upon initialization should happen before these two lines.
245-
//
246-
// Clear application lifetime metrics.
247-
await Context.metricsDatabase.clear(Lifetime.Application);
248-
// Initialize the events database.
249-
await Context.eventsDatabase.initialize();
250-
251243
// We need to mark Glean as initialized before dealing with the upload status,
252244
// otherwise we will not be able to submit deletion-request pings if necessary.
253245
//
254246
// This is fine, we are inside a dispatched task that is guaranteed to run before any
255247
// other task. No external API call will be executed before we leave this task.
256248
Context.initialized = true;
257249

250+
// IMPORTANT!
251+
// Any pings we want to send upon initialization should happen before this line.
252+
//
253+
// Clear application lifetime metrics.
254+
await Context.metricsDatabase.clear(Lifetime.Application);
255+
258256
// The upload enabled flag may have changed since the last run, for
259257
// example by the changing of a config file.
260258
if (uploadEnabled) {
@@ -285,9 +283,16 @@ class Glean {
285283
}
286284
}
287285

286+
// Initialize the events database.
287+
//
288+
// It's important this happens _after_ the upload state is dealt with,
289+
// because initializing the events database may record the execution_counter and
290+
// glean.restarted metrics. If the upload state is not defined these metrics can't be recorded.
291+
await Context.eventsDatabase.initialize();
292+
288293
// We only scan the pendings pings **after** dealing with the upload state.
289-
// If upload is disabled, we delete all pending pings files
290-
// and we need to do that **before** scanning the pending pings
294+
// If upload is disabled, pending pings files are deleted
295+
// so we need to know that state **before** scanning the pending pings
291296
// to ensure we don't enqueue pings before their files are deleted.
292297
await Context.pingsDatabase.scanPendingPings();
293298
});
@@ -505,22 +510,29 @@ class Glean {
505510
/**
506511
* Test-only API**
507512
*
508-
* Resets the Glean to an uninitialized state.
513+
* Resets Glean to an uninitialized state.
514+
* This is a no-op in case Glean has not been initialized.
509515
*
510516
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
517+
*
518+
* @param clearStores Whether or not to clear the events, metrics and pings databases on uninitialize.
511519
*/
512-
static async testUninitialize(): Promise<void> {
513-
// Get back to an uninitialized state.
514-
await Context.testUninitialize();
520+
static async testUninitialize(clearStores = true): Promise<void> {
521+
if (Context.initialized) {
522+
await Glean.shutdown();
515523

516-
// Shutdown the current uploader.
517-
//
518-
// This is fine because a new uploader is created on initialize.
519-
// It will also guarantee all pings to be sent before uninitializing.
520-
await Glean.pingUploader?.shutdown();
524+
if (clearStores) {
525+
await Context.eventsDatabase.clearAll();
526+
await Context.metricsDatabase.clearAll();
527+
await Context.pingsDatabase.clearAll();
528+
}
529+
530+
// Get back to an uninitialized state.
531+
Context.testUninitialize();
521532

522-
// Deregister all plugins
523-
testResetEvents();
533+
// Deregister all plugins
534+
testResetEvents();
535+
}
524536
}
525537

526538
/**
@@ -535,25 +547,15 @@ class Glean {
535547
* If disabled, all persisted metrics, events and queued pings (except
536548
* first_run_date) are cleared. Default to `true`.
537549
* @param config Glean configuration options.
550+
* @param clearStores Whether or not to clear the events, metrics and pings databases on reset.
538551
*/
539552
static async testResetGlean(
540553
applicationId: string,
541554
uploadEnabled = true,
542-
config?: ConfigurationInterface
555+
config?: ConfigurationInterface,
556+
clearStores = true,
543557
): Promise<void> {
544-
await Glean.testUninitialize();
545-
546-
// Clear the databases.
547-
try {
548-
await Context.eventsDatabase.clearAll();
549-
await Context.metricsDatabase.clearAll();
550-
await Context.pingsDatabase.clearAll();
551-
} catch {
552-
// Nothing to do here.
553-
// It is expected that these will fail in case we are initializing Glean for the first time.
554-
}
555-
556-
// Re-Initialize Glean.
558+
await Glean.testUninitialize(clearStores);
557559
await Glean.testInitialize(applicationId, uploadEnabled, config);
558560
}
559561
}

glean/src/core/upload/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
Observer as PingsDatabaseObserver,
1515
PingInternalRepresentation
1616
} from "../pings/database.js";
17+
import type PingsDatabase from "../pings/database.js";
1718
import {
1819
isDeletionRequest
1920
} from "../pings/database.js";
@@ -96,7 +97,7 @@ class PingUploader implements PingsDatabaseObserver {
9697
constructor(
9798
config: Configuration,
9899
platform: Platform,
99-
private readonly pingsDatabase = Context.pingsDatabase,
100+
private readonly pingsDatabase: PingsDatabase,
100101
private readonly policy = new Policy(),
101102
private readonly rateLimiter = new RateLimiter(RATE_LIMITER_INTERVAL_MS, MAX_PINGS_PER_INTERVAL)
102103
) {

0 commit comments

Comments
 (0)