From b1c0798e74271508cd00a064aef3b895a8a8e7f8 Mon Sep 17 00:00:00 2001 From: Darrian Date: Mon, 5 May 2025 21:35:49 +0100 Subject: [PATCH 1/4] change getPorts to resolve all responses via promises to resolve race condition --- core/serial.js | 94 ++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/core/serial.js b/core/serial.js index ece852a..880a478 100644 --- a/core/serial.js +++ b/core/serial.js @@ -546,54 +546,64 @@ To add a new serial device, you must add an object to return oldListener; }; - /* Calls 'callback(port_list, shouldCallAgain)' - 'shouldCallAgain==true' means that more devices - may appear later on (eg Bluetooth LE).*/ - var getPorts=function(callback) { - var ports = []; + /** + * List ports available over all configured devices. + * `shouldCallAgain` mean that more devices may appear later on (eg. Bluetooth LE) + * @param {(ports, shouldCallAgain) => void} callback + */ + var getPorts = function (callback) { var newPortToDevice = []; - // get all devices - var responses = 0; + var devices = Espruino.Core.Serial.devices; - if (!devices || devices.length==0) { - portToDevice = newPortToDevice; + if (!devices || devices.length == 0) { + portToDevice = newPortToDevice; return callback(ports, false); } - var shouldCallAgain = false; - devices.forEach(function (device) { - //console.log("getPorts -->",device.name); - device.getPorts(function(devicePorts, instantPorts) { - //console.log("getPorts <--",device.name); - if (instantPorts===false) shouldCallAgain = true; - if (devicePorts) { - devicePorts.forEach(function(port) { - var ignored = false; - if (Espruino.Config.SERIAL_IGNORE) - Espruino.Config.SERIAL_IGNORE.split("|").forEach(function(wildcard) { - var regexp = "^"+wildcard.replace(/\./g,"\\.").replace(/\*/g,".*")+"$"; - if (port.path.match(new RegExp(regexp))) - ignored = true; - }); - if (!ignored) { - if (port.usb && port.usb[0]==0x0483 && port.usb[1]==0x5740) - port.description = "Espruino board"; - ports.push(port); - newPortToDevice[port.path] = device; - } - }); - } - responses++; - if (responses == devices.length) { - portToDevice = newPortToDevice; - ports.sort(function(a,b) { - if (a.unimportant && !b.unimportant) return 1; - if (b.unimportant && !a.unimportant) return -1; - return 0; - }); - callback(ports, shouldCallAgain); - } + // Test to see if a given port path is ignore or not by configuration + function isIgnored(path) { + if (!Espruino.Config.SERIAL_IGNORE) return false; + + return Espruino.Config.SERIAL_IGNORE.split("|").some((wildcard) => { + const regexp = new RegExp( + `^${wildcard.replace(/\./g, "\\.").replace(/\*/g, ".*")}$` + ); + + return path.match(regexp); }); + } + + // Asynchronously call 'getPorts' on all devices and map results back as a series of promises + Promise.allSettled( + devices.map((device) => + new Promise((resolve) => device.getPorts(resolve)).then( + (devicePorts, instantPorts) => ({ + device: device.name, + shouldCallAgain: !instantPorts, // If the ports are not present now (eg. BLE) then call again + value: (devicePorts || []) + .filter((port) => !isIgnored(port.path)) // Filter out all the ignored ports + .map((port) => { + // Map a description for this particular Product/Vendor + if (port.usb && port.usb[0] == 0x0483 && port.usb[1] == 0x5740) + port.description = "Espruino board"; + return port; + }), + }) + ) + ) + ).then((ports) => { + // Reduce the responses to only promises that were fulfilled + const successfulPorts = ports.reduce((acc, promise) => { + if (promise.status === "fulfilled") acc.push(promise.value); + return acc; + }, []); + + callback( + successfulPorts + .map((val) => val.value) + .reduce((acc, port) => acc.concat(port), []), + successfulPorts.some((val) => val.shouldCallAgain) + ); }); }; From ec40231c04e70263db3026facb57bd983a28c702 Mon Sep 17 00:00:00 2001 From: Darrian Date: Mon, 5 May 2025 22:51:24 +0100 Subject: [PATCH 2/4] missed re-adding setting portToDevice --- core/serial.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/core/serial.js b/core/serial.js index 880a478..82e29b0 100644 --- a/core/serial.js +++ b/core/serial.js @@ -552,7 +552,7 @@ To add a new serial device, you must add an object to * @param {(ports, shouldCallAgain) => void} callback */ var getPorts = function (callback) { - var newPortToDevice = []; + var newPortToDevice = {}; var devices = Espruino.Core.Serial.devices; if (!devices || devices.length == 0) { @@ -578,7 +578,7 @@ To add a new serial device, you must add an object to devices.map((device) => new Promise((resolve) => device.getPorts(resolve)).then( (devicePorts, instantPorts) => ({ - device: device.name, + device: device, shouldCallAgain: !instantPorts, // If the ports are not present now (eg. BLE) then call again value: (devicePorts || []) .filter((port) => !isIgnored(port.path)) // Filter out all the ignored ports @@ -591,13 +591,22 @@ To add a new serial device, you must add an object to }) ) ) - ).then((ports) => { + ).then((devicePromises) => { // Reduce the responses to only promises that were fulfilled - const successfulPorts = ports.reduce((acc, promise) => { + const successfulPorts = devicePromises.reduce((acc, promise) => { if (promise.status === "fulfilled") acc.push(promise.value); return acc; }, []); + portToDevice = devicePromises.reduce((acc, promise) => { + if (promise.status === "fulfilled") + promise.value.value.forEach( + (port) => (acc[port.path] = promise.value.device) + ); + + return acc; + }, {}); + callback( successfulPorts .map((val) => val.value) @@ -614,10 +623,10 @@ To add a new serial device, you must add an object to var openSerialInternal=function(serialPort, connectCallback, disconnectCallback, attempts) { /* If openSerial is called, we need to have called getPorts first - in order to figure out which one of the serial_ implementations - we must call into. */ + in order to figure out which one of the serial_ implementations + we must call into. */ if (portToDevice === undefined) { - portToDevice = []; // stop recursive calls if something errors + portToDevice = {}; // stop recursive calls if something errors return getPorts(function() { openSerialInternal(serialPort, connectCallback, disconnectCallback, attempts); }); From 6652b052994da2c46b7f7cf5792df8c3147daf22 Mon Sep 17 00:00:00 2001 From: Darrian Date: Wed, 7 May 2025 11:05:34 +0100 Subject: [PATCH 3/4] switch to Promise.all --- core/serial.js | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/core/serial.js b/core/serial.js index 7a1bdca..cf6349d 100644 --- a/core/serial.js +++ b/core/serial.js @@ -574,7 +574,7 @@ To add a new serial device, you must add an object to } // Asynchronously call 'getPorts' on all devices and map results back as a series of promises - Promise.allSettled( + Promise.all( devices.map((device) => new Promise((resolve) => device.getPorts(resolve)).then( (devicePorts, instantPorts) => ({ @@ -591,27 +591,15 @@ To add a new serial device, you must add an object to }) ) ) - ).then((devicePromises) => { - // Reduce the responses to only promises that were fulfilled - const successfulPorts = devicePromises.reduce((acc, promise) => { - if (promise.status === "fulfilled") acc.push(promise.value); - return acc; - }, []); - - portToDevice = devicePromises.reduce((acc, promise) => { - if (promise.status === "fulfilled") - promise.value.value.forEach( - (port) => (acc[port.path] = promise.value.device) - ); - + ).then((results) => { + portToDevice = results.reduce((acc, promise) => { + promise.value.forEach((port) => (acc[port.path] = promise.device)); return acc; }, {}); callback( - successfulPorts - .map((val) => val.value) - .reduce((acc, port) => acc.concat(port), []), - successfulPorts.some((val) => val.shouldCallAgain) + results.flatMap((result) => result.value), + results.some((result) => result.shouldCallAgain) ); }); }; From 001492941687d4b3edc4a637e05d452ce4e7ce47 Mon Sep 17 00:00:00 2001 From: Darrian Date: Wed, 7 May 2025 19:15:00 +0100 Subject: [PATCH 4/4] re-added port sorting --- core/serial.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/serial.js b/core/serial.js index cf6349d..df9da7f 100644 --- a/core/serial.js +++ b/core/serial.js @@ -598,8 +598,14 @@ To add a new serial device, you must add an object to }, {}); callback( - results.flatMap((result) => result.value), - results.some((result) => result.shouldCallAgain) + results + .flatMap((result) => result.value) + .sort((a, b) => { + if (a.unimportant && !b.unimportant) return 1; + if (b.unimportant && !a.unimportant) return -1; + return 0; + }), + results.some((result) => result.shouldCallAgain), ); }); };