What is the best way to track the state of an Isolate in Dart? - dart-isolates

I'm trying to track whether the isolate is currently running or not (and in the future whether it has errored out) using isolate.addOnExitListener(...). However, the following snippet of code is not working how I would expect:
items.forEach((name, item) async {
Isolate isolate = await Isolate.spawnUri(...);
item.status = "running";
ReceivePort receivePort = new ReceivePort();
isolate.addOnExitListener(receivePort.sendPort);
receivePort.listen((message){
if (message == null) {
print("Item exited: ${item.name}");
item.status = "stopped";
}
});
});
The "items" map contains 3 values, each with a distinct name: item1, item2, item3
When I run this code, the only output I get is:
"Item exited: item3"
I expected the following output (not necessarily in order since isolates are asynchronous):
"Item exited: item1"
"Item exited: item2"
"Item exited: item3"
Here is the code being run in the isolates:
import 'dart:io';
main(List args) {
print('Hello world: standard out!');
stderr.writeln('Hello world: standard error!');
}
It seems like the closure is being lost. Am I doing something wrong here? Is there a better way to track the state of an isolate?
Thanks in advance!

If you want to make absolutely sure that you can install onExit and onError listeners in an isolate before any of the isolate's code executes, then you can spawn the isolate paused. See documentation about spawnUri.
Here is an example:
var isolate = await Isolate.spawnUri(myUri, args, message, paused: true);
var receivePort = new ReceivePort();
isolate.addOnExitListener(receivePort.sendPort);
receivePort.listen((message){
if (message == null) { // A null message means the isolate exited
print("Item exited: ${item.name}");
item.status = "stopped";
}
});
isolate.resume(isolate.pauseCapability);
Once you have registered the appropriate listeners, you can start the newly created isolate with resume.
This is very similar to the suggestion of an initial handshake, but in this case it is builtin to the library.
Hope this helps,
-Ivan

I had the same behavior when the isolate didn't do anything notable (just one print statement). It seems it exited before the onExitListener was registered.
DartDoc of onExitListener says
If the isolate is already dead, no message will be sent.
The isolate code
import 'dart:async' show Future, Stream;
void main(List<String> args) {
new Future.delayed(new Duration(milliseconds: 500),
() =>print('isolate ${args}'));
}
With the additional delay I got the desired on exit notification.
The delay needs to be quite high :-(.
You can do some initial handshake to ensure the isolate doesn't exit before everything is set up properly
import 'dart:isolate';
import 'dart:async' show Future, Stream, Completer;
import 'dart:io' as io;
class Item {
String name;
String status;
Item(this.name);
}
void main() {
final items = {'a': new Item('a'), 'b': new Item('b'), 'c': new Item('c')};
items.forEach((name, item) async {
ReceivePort receivePort = new ReceivePort();
SendPort sendPort = receivePort.sendPort;
Isolate isolate = await Isolate.spawnUri(
Uri.parse('isolate.dart'), [sendPort, name], null);
receivePort.listen((message) {
if (message is SendPort) {
message.send('connected');
} else if (message == null) {
print("Item exited: ${item.name}");
item.status = "stopped";
} else {
print("Message: ${message}");
}
});
isolate.addOnExitListener(receivePort.sendPort);
item.status = "running";
});
}
import 'dart:isolate';
void main(List<String> args) {
SendPort sendPort = (args[0] as SendPort);
var receivePort = new ReceivePort();
sendPort.send(receivePort.sendPort);
// keeps the isolate alive at least until the first messgae arrives
receivePort.first.then((e) => print('isolate received: $e'));
}

Related

perform network call and proceed - asynchronous task

i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}

Combine: Wrapped async call with Future, but Future.sink doesn't appear to complete

First-time poster, long-time reader ... I've wrapped an async call to the Firebase Authorization API. I'm calling it from inside a SwiftUI View function.
func authenticateFirebaseEmail(email: String, password: String) ->
Future<String, Error> {
return Future<String,Error> { promise in
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error=error {
print("failure detected")
promise(.failure(error))
}
if let result=result {
print("result detected - returning success promise")
promise(.success(result.user.email!))
}
}
}
}
...
func logMeInFuncInView() {
var cancellable : AnyCancellable?
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
.sink(receiveCompletion: { (completion) in
print("completion received")
}, receiveValue: { user in
print("value received")
self.errorMessage = user
})
}
The console output is as follows, but never reaches the "completion received" or "value received" calls:
result detected - returning successful promise
Is the issue with the wrapped callback, the future, the way I'm using the future, or something that I'm not seeing entirely?
Your cancellable is local variable, so destroyed once went off context. As soon as subscriber is destroyed it cancels subscription and, as it is only one, publisher cancelled as well.
Your solution is to make your cancellable as property, ie
var cancellable : AnyCancellable? // << here !!
func logMeInFuncInView() {
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
// .. other code
}

ClientException, and i can't print the returned value (the request body)

Alright i'm losing my mind here,
in my flutter app, i'm using this function to perform post requests :
Future<Map> postRequest(String serviceName, Map<String, dynamic> data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
http.Response response = await http.post(
_urlBase + '$_serverApi$serviceName',
body: jsonEncode(data),
);
if (response.statusCode == 200) {
responseBody = jsonDecode(response.body);
//
// If we receive a new token, let's save it
//
if (responseBody["status"] == "TOKEN") {
await _setMobileToken(responseBody["data"]);
// TODO: rerun the Post request
}
}
} catch (e) {
// An error was received
throw new Exception("POST ERROR");
}
return responseBody;
}
The problems are :
I get a ClientException (Not every time)
In another class, I stored the result of this function in a variable, it's supposed to return a Future<Map<dynamic, dynamic>>, when i printed it it shows :
I/flutter ( 9001): Instance of 'Future<Map<dynamic, dynamic>>'
But when i run the same post request directly (without using a function) it worked, and it shows the message that i was waiting for.
note: in both cases (function or not), in the server side it was the same thing.
this is the function where i used the post request:
void _confirm() {
if (_formKey.currentState.saveAndValidate()) {
print(_formKey.currentState.value);
var v = auth.postRequest("se_connecter", _formKey.currentState.value);
print(v);
} else {
print(_formKey.currentState.value);
print("validation failed");
}
}
Well for the second problem, i just did these changes:
void _confirm() async {
and
var v = await auth.postRequest('se_connecter', _formKey.currentState.value);
and yes it is stupid.
For the exception, it was the ssl encryption that caused it, so i removed it from my backend.

dart pass data from stdin and stderr to stdin of another process

This is really a follow in question from:
Dart: How to pass data from one process to another via streams
I using dart to spawn two processes.
Lets call these two processes 'lhs' and 'rhs'.
(lhs - left hand side and rhs - right hand side).
The first process (lhs) writes to stdout and stderr.
I need to pipe all the data from the first process (lhs) to stdin of the second process (rhs).
In the above noted stack overflow the answer was to use the 'pipe' method to stream data from lhs.stdout to rhs.stdin.
Given I now want to write data from both of lhs' streams (stdout and stderr) the pipe method doesn't work as it only supports a single stream and you can't call pipe twice on rhs' stdin (an error is thrown stating correctly that you can't call addStream twice).
So I've tried the following code which seems to partially work but I only see the first character from lhs' stderr stream and then everything completes (the onDone methods are called on both of lhs' stream).
Some detail to help understand what is going on here.
In the below code the 'lhs' is a call to 'dart --version'. When dart writes out its version string it writes it to stderr with nothing being written to stdout.
I use 'head' as the second process - 'rhs'. Its job is to simply received the combined output of stdout and stderr from lhs and print it to the console.
The output from a run of the below code is:
lhs exitCode=0
done
listen: stderr
listen: stderr written
done err
import 'dart:async';
import 'dart:cli';
import 'dart:io';
Future<void> main() async {
var dart = start('dart', ['--version']);
var head = start('head', ['-n', '5']);
pipeTo(dart, head);
}
void pipeTo(Future<Process> lhs, Future<Process> rhs) {
var complete = Completer<void>();
// wait for the lhs and rhs processes to
// start and then start piping lhs
// output to the rhs input.
lhs.then((lhsProcess) {
rhs.then((rhsProcess) {
// write stdout from lhs to stdin of rhs
lhsProcess.stdout.listen((datum) {
print('listen');
rhsProcess.stdin.add(datum);
print('listen written');
}
, onDone: () {
print('done');
complete.complete();
}
, onError: (Object e, StackTrace s) =>
print('onError $e')
,cancelOnError: true);
// write stderr from lhs to stdin of rhs.
lhsProcess.stderr.listen((datum) {
print('listen: stderr');
rhsProcess.stdin.add(datum);
print('listen: stderr written');
}
, onDone: () {
print('done err');
if (!complete.isCompleted) complete.complete();
}
, onError: (Object e, StackTrace s) =>
print('onError $e')
, cancelOnError: true);
lhsProcess.exitCode.then((exitCode) {
print('lhs exitCode=$exitCode');
});
rhsProcess.exitCode.then((exitCode) {
print('rhs exitCode=$exitCode');
});
});
});
waitFor(complete.future);
}
Future<Process> start(String command, List<String> args) async {
var process = Process.start(
command,
args,
);
return process;
}
I think your solution of using listen() to forward events to rhsProcess.stdin will work. You could clean it up by using await in place of the then() callbacks and remove the Completer. You could make pipeTo() return a Future<void> and alternatively use waitFor(pipeTo()) if desired.
Here's a condensed example:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
void main() {
var dart = Process.start('dart', ['--version']);
var head = Process.start('head', ['-n', '1']);
pipeTo(dart, head);
}
Future<void> pipeTo(Future<Process> lhs, Future<Process> rhs) async {
var lhsProcess = await lhs;
var rhsProcess = await rhs;
lhsProcess.stdout.listen(rhsProcess.stdin.add);
lhsProcess.stderr.listen(rhsProcess.stdin.add);
rhsProcess.stdout.transform(utf8.decoder).listen(print);
}
Note: I pass -n 1 to head otherwise the program never exits.
Future<void> main() async {
var dart = start('cat', ['/var/log/syslog']);
var head = start('head', ['-n', '5']);
await pipeTo2(dart, head);
}
Future<void> pipeTo2(Future<Process> lhs, Future<Process> rhs) async {
// wait for both processes to start
var lhsProcess = await lhs;
var rhsProcess = await rhs;
// send the lhs stdout to the rhs stdin
lhsProcess.stdout.listen(rhsProcess.stdin.add);
// send the lhs stderr to the rhs stdin
lhsProcess.stderr.listen(rhsProcess.stdin.add).onDone(() {
rhsProcess.stdin.close();
});
// send the rhs stdout and stderr to the console
rhsProcess.stdout.listen(stdout.add);
rhsProcess.stderr.listen(stderr.add);
// wait for rhs to finish.
// if it finishes before the lhs does we will get
// a broken pipe error which is fine so we can
// suppress it.
await rhsProcess.stdin.done.catchError(
(Object e) {
// forget broken pipe after rhs terminates before lhs
},
test: (e) => e is SocketException && e.osError.message == 'Broken pipe',
);
}
In the case you have to wait the first process to end (but you should be able to implement this easily) I use something like this:
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var result = await Process.run('dart', ['--version']);
if (result.exitCode != 0) {
throw StateError(result.stderr);
}
var process = await Process.start('head', ['-n', '1']);
var buffer = StringBuffer();
var errBuffer = StringBuffer();
// ignore: unawaited_futures
process.stdout
.transform(utf8.decoder)
.forEach((_value) => buffer.write(_value));
// ignore: unawaited_futures
process.stderr
.transform(utf8.decoder)
.forEach((_value) => errBuffer.write(_value));
process.stdin.writeln('${result.stdout}${result.stderr}');
var exitCode = await process.exitCode;
if (exitCode != 0) {
throw StateError('$exitCode - $errBuffer');
}
print('$buffer');
}

DART async/await not waiting

So I've been working on experimenting with DART (whereby my core languages are C++, and embedded C derivates). Hence my code is probably not pretty as I'm more of a procedural programmer, but I'm getting by and learning... I've been struggling around Futures pertaining to await sync, and basically, I simply can't get DART to WAIT. The following code establishes a socket connection to a small embedded device and extracts info. That all works, but notice the order of operations SHOULD be main() gets some info from the console, then should call the method cardStatus to run off and get the info from the embedded device via the socket connection. This is where the await should occur. When the Future is returned, it should go off to the printstuff() method. I've added print statements that should go in order and read:
This should print 1st
This should print 2nd
This should print 3rd
Instead since the wait is not occurring on the cardstatus call (which is time consuming), I get:
This should print 1st
This should print 3rd
This should print 2nd
I've followed another thread on the use of async, and seem to be at least following one solid way of using this Other thread (I tried a .then with a completer with a similar result, so there is something core I feel I'm missing).. but I've been stuck on this for a week.
Code below, along with the console output.
import 'dart:io';
import 'dart:async' show Future;
const String STATUS = "#111111;";
String defaultIP = "10.1.14.202";
int defaultConfigPort = 5111;
int defaultControlPort = 6722;
var card = new Map();
getInput(String defaults) {
String takenin = stdin.readLineSync();
if (takenin == '') takenin = defaults;
return takenin;
}
Future main() async {
stdout.write('What is the IP address of the card ($defaultIP): ');
String ipaddress = getInput(defaultIP);
defaultIP = ipaddress;
print ("This should print 1st");
stdout.writeln("Connecting to $defaultIP");
await cardStatus(defaultIP, defaultConfigPort, STATUS, card);
printstuff();
}
printstuff() {
stdout.writeln(card['subnet']);
print ("This should print 3rd");
}
Future cardStatus(String ip, int port, String message, Map card) {
return new Future.delayed(Duration.ZERO, () {
Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
print(new String.fromCharCodes(data).trim());
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1);
print ("This should print 2nd");
//var card = new Map();
card['ip'] = str1[0];
card['subnet'] = str1[1];
card['gateway'] = str1[2];
card['unknown'] = str1[3];
card['persist'] = str1[4] == 'true';
card['build'] = str1[5];
card['serial'] = str1[6].substring(0, 14);
card['cloudpassword'] = str1[6].substring(14, 20);
card['DNS'] = str1[7];
card['cloudhost'] = str1[8];
card['cloudenabled'] = str1[9] == 'true';
print(card['ip']);
},
onDone: () {
print("Done");
socket.destroy();
});
//Send the request
socket.write(message);
});
});
}
and this is the current console output. notice the null shouldn't be a null if the cardStatus would have completed it would be printed str1.
What is the IP address of the card (10.1.14.202):
This should print 1st
Connecting to 10.1.14.202
null
This should print 3rd
Connected to: 10.1.14.202:5111
>10.1.14.202,255.255.255.0,10.1.14.1,,0,435,F44900A60040F8000000,192.168.1.1,connect.tutuuu.com,0;
[>10.1.14.202, 255.255.255.0, 10.1.14.1, , 0, 435, F44900A60040F8000000, 192.168.1.1, connect.tutuuu.com, 0;]
This should print 2nd
10.1.14.202
Done
Process finished with exit code 0
Thanks for all the help!
You are missing return before Socket.connect. As it stands now your code just starts connecting but never awaits it through future. I would highly recommend using as much as possible the new await / async syntax.
Here is a running example that does get google homepage:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
printstuff();
}
printstuff() {
print("This should print 3rd");
}
Future cardStatus(String ip, int port, String message) {
return new Future.delayed(Duration.ZERO, () {
return Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1.first);
print("This should print 2nd");
}, onDone: () {
print("Done");
socket.destroy();
}, onError: (e) {
print("Error while listening: $e");
});
socket.write(message);
});
});
}
Below slightly redacted version that uses awaits, and try / catch to handle errors:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
print("This should print 3rd");
}
Future<String> cardStatus(String ip, int port, String message) async {
var socket = await Socket.connect(ip, port);
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.write(message);
print("Sent request");
try {
var response = await socket.fold(
'',
(String acc, List<int> data) =>
acc + new String.fromCharCodes(data).trim());
print("Received response: ${response.substring(0, 10)}");
return response;
} finally {
socket.close();
}
}
I know it was answered but the question is great and I struggled myself with the concept so here is another element that got me to understand. in the dartpad (https://dartpad.dartlang.org/) try this (comments are in the code):
import 'dart:async';
//Just creating a duration to use later
Duration duration = new Duration(milliseconds: 500);
void main() {
//This is what tricked me, printStuff is async so running in parallel processing by default
//There is no need to call the function in a certain way (like a go xxx for a goroutine)
//there is an await in the function so it will wait inside the function only
//i.e. printStuff('a') starts then printStuff('b') starts straight away...already in prallel processing
//Run it and check the output
printStuff('a');
printStuff('b');
//Basically the await is in the function so printStuff is still returning a Future
//i.e. printStuff('a') starts but doesn't wait to complete to start printStuff('b')
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
//this await is waiting for the command to complete to move to the next iteration...
//the i iterations are done one after the other
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
Then try this:
import 'dart:async';
Duration duration = new Duration(milliseconds: 500);
//becuase I use await in main now, I must make it return a future and be async
Future main() async {
//to make it happen one after the other, you need await at a call function level
await printStuff('a');
await printStuff('b');
//Basically this says complete printStuff('a'), then start printStuff('b')...
//and yes technically one doesn't need the second await becuase there is nothing after
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
So my personal misunderstanding was that an async function is called in parallel straight away and an await in a function waits for real but in the function itself, with no impact on other parallel processing happening.

Resources