зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1588741 - Descriptors.getTarget may return unattached targets when called in parallel. r=jdescottes
The race isn't trivial to reproduce and the test do not reproduce it. But this was making target list tests to fail. Differential Revision: https://phabricator.services.mozilla.com/D48856 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
81acb75360
Коммит
be1b551d62
|
@ -42,15 +42,41 @@ add_task(async function() {
|
||||||
|
|
||||||
const { processes } = await mainRoot.listProcesses();
|
const { processes } = await mainRoot.listProcesses();
|
||||||
|
|
||||||
|
// Assert that concurrent calls to getTarget resolves the same target and that it is already attached
|
||||||
|
// We that, we were chasing a precise race, where a second call to ProcessDescriptor.getTarget()
|
||||||
|
// happens between the instantiation of ContentProcessTarget and its call to attach() from getTarget
|
||||||
|
// function.
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
processes.map(async processDescriptor => {
|
processes.map(async processDescriptor => {
|
||||||
const process = await processDescriptor.getTarget();
|
const promises = [];
|
||||||
|
const concurrentCalls = 10;
|
||||||
|
for (let i = 0; i < concurrentCalls; i++) {
|
||||||
|
const targetPromise = processDescriptor.getTarget();
|
||||||
|
// Every odd runs, wait for a tick to introduce some more randomness
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
await wait(0);
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
targetPromise.then(target => {
|
||||||
is(
|
is(
|
||||||
process.descriptorFront,
|
target.descriptorFront,
|
||||||
processDescriptor,
|
processDescriptor,
|
||||||
"Got the correct descriptorFront from the process target."
|
"Got the correct descriptorFront from the process target."
|
||||||
);
|
);
|
||||||
|
// activeConsole is one of the only attribute to assess that a Content Process Target is attached
|
||||||
|
ok(target.activeConsole, "The target is attached");
|
||||||
|
return target;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const targets = await Promise.all(promises);
|
||||||
|
for (let i = 1; i < concurrentCalls; i++) {
|
||||||
|
is(
|
||||||
|
targets[0],
|
||||||
|
targets[i],
|
||||||
|
"All the targets returned by concurrent calls to getTarget are the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -73,15 +99,39 @@ add_task(async function() {
|
||||||
|
|
||||||
const mainProcess = await mainRoot.getMainProcess();
|
const mainProcess = await mainRoot.getMainProcess();
|
||||||
const { frames } = await mainProcess.listRemoteFrames();
|
const { frames } = await mainProcess.listRemoteFrames();
|
||||||
await Promise.all(
|
|
||||||
frames.map(async frameDescriptorFront => {
|
|
||||||
const frameTargetFront = await frameDescriptorFront.getTarget();
|
|
||||||
|
|
||||||
|
// Assert that concurrent calls to getTarget resolves the same target and that it is already attached
|
||||||
|
await Promise.all(
|
||||||
|
frames.map(async frameDescriptor => {
|
||||||
|
const promises = [];
|
||||||
|
const concurrentCalls = 10;
|
||||||
|
for (let i = 0; i < concurrentCalls; i++) {
|
||||||
|
const targetPromise = frameDescriptor.getTarget();
|
||||||
|
// Every odd runs, wait for a tick to introduce some more randomness
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
await wait(0);
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
targetPromise.then(target => {
|
||||||
is(
|
is(
|
||||||
frameTargetFront.descriptorFront,
|
target.descriptorFront,
|
||||||
frameDescriptorFront,
|
frameDescriptor,
|
||||||
"Got the correct descriptorFront from the frame target."
|
"Got the correct descriptorFront from the frame target."
|
||||||
);
|
);
|
||||||
|
// traits is one attribute to assert that a Frame Target is attached
|
||||||
|
ok(target.traits, "The target is attached");
|
||||||
|
return target;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const targets = await Promise.all(promises);
|
||||||
|
for (let i = 1; i < concurrentCalls; i++) {
|
||||||
|
is(
|
||||||
|
targets[0],
|
||||||
|
targets[i],
|
||||||
|
"All the targets returned by concurrent calls to getTarget are the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -43,33 +43,42 @@ class FrameDescriptorFront extends FrontClassWithSpec(frameDescriptorSpec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTarget() {
|
async getTarget() {
|
||||||
|
// Only return the cached Target if it is still alive.
|
||||||
|
// It may have been destroyed in case of navigation trigerring a load in another
|
||||||
|
// process.
|
||||||
if (this._frameTargetFront && this._frameTargetFront.actorID) {
|
if (this._frameTargetFront && this._frameTargetFront.actorID) {
|
||||||
return this._frameTargetFront;
|
return this._frameTargetFront;
|
||||||
}
|
}
|
||||||
|
// Otherwise, ensure that we don't try to spawn more than one Target by
|
||||||
|
// returning the pending promise
|
||||||
if (this._targetFrontPromise) {
|
if (this._targetFrontPromise) {
|
||||||
return this._targetFrontPromise;
|
return this._targetFrontPromise;
|
||||||
}
|
}
|
||||||
this._targetFrontPromise = (async () => {
|
this._targetFrontPromise = (async () => {
|
||||||
|
let target = null;
|
||||||
try {
|
try {
|
||||||
const targetForm = await super.getTarget();
|
const targetForm = await super.getTarget();
|
||||||
|
// getTarget uses 'json' in the specification type and this prevents converting
|
||||||
|
// actor exception into front exceptions
|
||||||
if (targetForm.error) {
|
if (targetForm.error) {
|
||||||
this._targetFrontPromise = null;
|
throw new Error(targetForm.error);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
this._frameTargetFront = await this._createFrameTarget(targetForm);
|
target = await this._createFrameTarget(targetForm);
|
||||||
await this._frameTargetFront.attach();
|
await target.attach();
|
||||||
// clear the promise if we are finished so that we can re-connect if
|
|
||||||
// necessary
|
|
||||||
this._targetFrontPromise = null;
|
|
||||||
return this._frameTargetFront;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// This is likely to happen if we get a lot of events which drop previous
|
// This is likely to happen if we get a lot of events which drop previous
|
||||||
// frames.
|
// frames.
|
||||||
console.log(
|
console.log(
|
||||||
`Request to connect to frameDescriptor "${this.id}" failed: ${e}`
|
`Request to connect to frameDescriptor "${this.id}" failed: ${e}`
|
||||||
);
|
);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
// Save the reference to the target only after the call to attach
|
||||||
|
// so that getTarget always returns the attached target in case of concurrent calls
|
||||||
|
this._frameTargetFront = target;
|
||||||
|
// clear the promise if we are finished so that we can re-connect if
|
||||||
|
// necessary
|
||||||
|
this._targetFrontPromise = null;
|
||||||
|
return target;
|
||||||
})();
|
})();
|
||||||
return this._targetFrontPromise;
|
return this._targetFrontPromise;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,31 +55,35 @@ class ProcessDescriptorFront extends FrontClassWithSpec(processDescriptorSpec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTarget() {
|
async getTarget() {
|
||||||
|
// Only return the cached Target if it is still alive.
|
||||||
if (this._processTargetFront && this._processTargetFront.actorID) {
|
if (this._processTargetFront && this._processTargetFront.actorID) {
|
||||||
return this._processTargetFront;
|
return this._processTargetFront;
|
||||||
}
|
}
|
||||||
|
// Otherwise, ensure that we don't try to spawn more than one Target by
|
||||||
|
// returning the pending promise
|
||||||
if (this._targetFrontPromise) {
|
if (this._targetFrontPromise) {
|
||||||
return this._targetFrontPromise;
|
return this._targetFrontPromise;
|
||||||
}
|
}
|
||||||
this._targetFrontPromise = (async () => {
|
this._targetFrontPromise = (async () => {
|
||||||
|
let targetFront = null;
|
||||||
try {
|
try {
|
||||||
const targetForm = await super.getTarget();
|
const targetForm = await super.getTarget();
|
||||||
this._processTargetFront = await this._createProcessTargetFront(
|
targetFront = await this._createProcessTargetFront(targetForm);
|
||||||
targetForm
|
await targetFront.attach();
|
||||||
);
|
|
||||||
await this._processTargetFront.attach();
|
|
||||||
// clear the promise if we are finished so that we can re-connect if
|
|
||||||
// necessary
|
|
||||||
this._targetFrontPromise = null;
|
|
||||||
return this._processTargetFront;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// This is likely to happen if we get a lot of events which drop previous
|
// This is likely to happen if we get a lot of events which drop previous
|
||||||
// processes.
|
// processes.
|
||||||
console.log(
|
console.log(
|
||||||
`Request to connect to ProcessDescriptor "${this.id}" failed: ${e}`
|
`Request to connect to ProcessDescriptor "${this.id}" failed: ${e}`
|
||||||
);
|
);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
// Save the reference to the target only after the call to attach
|
||||||
|
// so that getTarget always returns the attached target in case of concurrent calls
|
||||||
|
this._processTargetFront = targetFront;
|
||||||
|
// clear the promise if we are finished so that we can re-connect if
|
||||||
|
// necessary
|
||||||
|
this._targetFrontPromise = null;
|
||||||
|
return targetFront;
|
||||||
})();
|
})();
|
||||||
return this._targetFrontPromise;
|
return this._targetFrontPromise;
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче