Skip to content

Commit

Permalink
fix: hard reset should clear pending commands
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobrosenberg committed Apr 20, 2022
1 parent 223b4d0 commit 94940cb
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 20 deletions.
6 changes: 5 additions & 1 deletion src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ class Commands {
* @param {DeviceTreeItem} treeItem
*/
resetDevice: async ({ device }) => {
device.adapter.reset({ broadcastOutputAsTerminalData: true, softReset: false });
// resetting the device should also reset the waiting calls
device.adapter.__proxyMeta.clearQueue();
// we don't want a stalled call to block the device
device.adapter.__proxyMeta.skipCurrent();
device.adapter.__proxyMeta.target.reset({ broadcastOutputAsTerminalData: true, softReset: false });
},
/**
* Soft reboot device
Expand Down
49 changes: 34 additions & 15 deletions src/utils/blockingProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const friendlyProxyQueueItem = (item) => {
* @template T
* @param {T} _target
* @param {Object} [_options]
* @param {(string|symbol)[]} _options.exceptions methods that should not be queued
* @param {(string|symbol)[]=} _options.exceptions methods that should not be queued
* @param {BeforeEachCall<T>} _options.beforeEachCall
* @returns {BlockingProxy<T>}
*/
Expand Down Expand Up @@ -82,12 +82,16 @@ class BlockingProxyQueueItem {
}

async exec() {
this.startedAt = new Date();
const { target, field, args } = this;
await this.options.beforeEachCall(target, field, args);
const result = await target[field].bind(target)(...args);
this.finishedAt = new Date();
return result;
try {
this.startedAt = new Date();
const { target, field, args } = this;
await this.options.beforeEachCall(target, field, args);
const result = await target[field].bind(target)(...args);
this.finishedAt = new Date();
this.resolve(result);
} catch (err) {
this.reject(err);
}
}

get waitDuration() {
Expand All @@ -112,8 +116,11 @@ class ProxyMeta {
/** @type {BlockingProxyQueueItem} */
this.lastCall = null;

// processQueue replaces this.idle with a resolveable promise
this.idle = { then: (cb) => cb() };
this.idle = resolvablePromise();

// Proxy starts in idle mode.
// Calls to processQueue will replace the idle prop with a new promise.
this.idle.resolve();

/**@type {BlockingProxyQueueItem[]} */
this.history = [];
Expand Down Expand Up @@ -153,20 +160,32 @@ class ProxyMeta {
this.isBusy = true;

while (this.queue.length) {
this.skipQueue = resolvablePromise();
const queueItem = this.queue.shift();
this.history.push(queueItem);
this.lastCall = queueItem;
try {
const result = await queueItem.exec();
queueItem.resolve(result);
} catch (err) {
queueItem.reject(err);
}
// continue once this call is resolved or proxy receives a skipQueue call
await Promise.race([queueItem.exec(), this.skipQueue]);
}

this.idle.resolve();
this.isBusy = false;
}

/** Clears the queue. */
clearQueue() {
this.queue.length = 0;
}

/**
* Skips the current running call
* The skipped call will continue as normal,
* but will be removed from the queue.
* Only unskipped calls affect the idle status of the proxy.
*/
skipCurrent() {
this.skipQueue.resolve();
}
}

module.exports = { createBlockingProxy, friendlyProxyQueueItem };
Expand Down
73 changes: 69 additions & 4 deletions src/utils/specs/blockingProxy.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { createBlockingProxy } from "../blockingProxy.js";

const wait = (time) => new Promise((resolve) => setTimeout(resolve, time));

const createTestObj = () => {
const obj = {
events: [],
runCallback: (callback, delay) =>
new Promise((resolve) =>
setTimeout(() => {
callback();
resolve();
}, delay)
),
async20: () =>
new Promise((resolve) =>
setTimeout(() => {
Expand All @@ -21,6 +30,7 @@ const createTestObj = () => {
return obj;
};

// this doesn't test blockingProxy, just the test object
test("unblocked object runs methods in parallel", async () => {
const obj = createTestObj();
const startStamp = Date.now();
Expand All @@ -42,9 +52,9 @@ test("blocked object runs methods in sequence", async () => {
const proxied = createBlockingProxy(obj);
const promises = [proxied.async20(), proxied.async20(), proxied.async20(), proxied.async20(), proxied.async20()];
Promise.all(promises).then(() => (finishedLastTestStamp = Date.now()));

// start proxy
proxied.__proxyMeta.run()
proxied.__proxyMeta.run();
proxied.__proxyMeta.idle.then(() => (isReadyTestStamp = Date.now()));
await Promise.all([...promises, proxied.__proxyMeta.idle]);

Expand Down Expand Up @@ -75,9 +85,9 @@ test("blocked object can have beforeEachCall hook and exceptions", async () => {
];

Promise.all(promises).then(() => (finishedLastTestStamp = Date.now()));

// start proxy
proxied.__proxyMeta.run()
proxied.__proxyMeta.run();
proxied.__proxyMeta.idle.then(() => (isReadyTestStamp = Date.now()));

await Promise.all([...promises, proxied.__proxyMeta.idle]);
Expand All @@ -102,3 +112,58 @@ test("blocked object can have beforeEachCall hook and exceptions", async () => {
"async10",
]);
});

test("can clear queue", async () => {
const obj = createTestObj();
const proxied = createBlockingProxy(obj);

const messages = [];

proxied.runCallback(() => messages.push("hello"), 20);
proxied.runCallback(() => messages.push("world"), 20);
proxied.runCallback(() => messages.push("how are you"), 20);

proxied.__proxyMeta.run();

// clear queue in the middle of the second call
await wait(30);
proxied.__proxyMeta.clearQueue();
await proxied.__proxyMeta.idle;

assert.deepEqual(messages, ["hello", "world"]);
});

test("can skip current call", async () => {
const obj = createTestObj();
const proxied = createBlockingProxy(obj);

const messages = [];

proxied.runCallback(() => messages.push("hello"), 60);
proxied.runCallback(() => messages.push("world"), 20);
proxied.runCallback(() => messages.push("how"), 20);
proxied.runCallback(() => messages.push("are"), 20);
proxied.runCallback(() => messages.push("you"), 20);

proxied.__proxyMeta.run();
proxied.__proxyMeta.skipCurrent();
await proxied.__proxyMeta.idle;
assert.deepEqual(messages, ["world", "how", "hello", "are", "you"]);
});

test("a skipped call doesnt keep the queue active", async () => {
const obj = createTestObj();
const proxied = createBlockingProxy(obj);

const messages = [];

proxied.runCallback(() => messages.push("hello"), 20);

proxied.__proxyMeta.run();
proxied.__proxyMeta.skipCurrent();
await proxied.__proxyMeta.idle;
assert.deepEqual(messages, []);

await wait(20);
assert.deepEqual(messages, ["hello"]);
});

0 comments on commit 94940cb

Please sign in to comment.