Skip to content

Change getPorts to resolve all responses via promises to resolve race condition #195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 8, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 57 additions & 44 deletions core/serial.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,54 +546,67 @@ 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 = [];
var newPortToDevice = [];
// get all devices
var responses = 0;
/**
* 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 = {};

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) {
// 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.all(
devices.map((device) =>
new Promise((resolve) => device.getPorts(resolve)).then(
(devicePorts, instantPorts) => ({
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
.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((results) => {
portToDevice = results.reduce((acc, promise) => {
promise.value.forEach((port) => (acc[port.path] = promise.device));
return acc;
}, {});

callback(
results
.flatMap((result) => result.value)
.sort((a, b) => {
if (a.unimportant && !b.unimportant) return 1;
if (b.unimportant && !a.unimportant) return -1;
return 0;
});
callback(ports, shouldCallAgain);
}
});
}),
results.some((result) => result.shouldCallAgain),
);
});
};

Expand All @@ -604,10 +617,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);
});
Expand Down
Loading