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');
}
Related
I want to run any arbitrary bash command from Deno, like I would with a child_process in Node. Is that possible in Deno?
In order to run a shell command, you have to use Deno.run, which requires --allow-run permissions.
There's an ongoing discussion to use --allow-all instead for running a subprocess
The following will output to stdout.
// --allow-run
const process = Deno.run({
cmd: ["echo", "hello world"]
});
// Close to release Deno's resources associated with the process.
// The process will continue to run after close(). To wait for it to
// finish `await process.status()` or `await process.output()`.
process.close();
If you want to store the output, you'll have to set stdout/stderr to "piped"
const process = Deno.run({
cmd: ["echo", "hello world"],
stdout: "piped",
stderr: "piped"
});
const output = await process.output() // "piped" must be set
const outStr = new TextDecoder().decode(output);
/*
const error = await p.stderrOutput();
const errorStr = new TextDecoder().decode(error);
*/
process.close();
Deno 1.28.0 added a new API (unstable) to run a shell command: Deno.Command
let cmd = new Deno.Command("echo", { args: ["hello world"] });
let { stdout, stderr } = await cmd.output();
// stdout & stderr are a Uint8Array
console.log(new TextDecoder().decode(stdout)); // hello world
More advanced usage:
const c = new Deno.Command("cat", { stdin: "piped" });
c.spawn();
// open a file and pipe input from `cat` program to the file
const file = await Deno.open("output.txt", { write: true });
await c.stdout.pipeTo(file.writable);
const stdin = c.stdin.getWriter();
await stdin.write(new TextEncoder().encode("foobar"));
await stdin.close();
const s = await c.status;
console.log(s);
--unstable flag is required to use Deno.Command
This API will most likely replace Deno.run
Make sure to await status or output of the child process created with Deno.run.
Otherwise, the process might be killed, before having executed any code. For example:
deno run --allow-run main.ts
main.ts:
const p = Deno.run({
cmd: ["deno", "run", "--allow-write", "child.ts"],
});
const { code } = await p.status(); // (*1); wait here for child to finish
p.close();
child.ts:
// If we don't wait at (*1), no file is written after 3 sec delay
setTimeout(async () => {
await Deno.writeTextFile("file.txt", "Some content here");
console.log("finished!");
}, 3000);
Pass arguments via stdin / stdout:
main.ts:
const p = Deno.run({
cmd: ["deno", "run", "--allow-write", "child.ts"],
// Enable pipe between processes
stdin: "piped",
stdout: "piped",
stderr: "piped",
});
if (!p.stdin) throw Error();
// pass input to child
await p.stdin.write(new TextEncoder().encode("foo"));
await p.stdin.close();
const { code } = await p.status();
if (code === 0) {
const rawOutput = await p.output();
await Deno.stdout.write(rawOutput); // could do some processing with output
} else { /* error */ }
child.ts:
import { readLines } from "https://deno.land/std/io/bufio.ts"; // convenient wrapper
// read given input argument
let args = "";
for await (const line of readLines(Deno.stdin)) {
args += line;
}
setTimeout(async () => {
await Deno.writeTextFile("file.txt", `Some content here with ${args}`);
console.log(`${args} finished!`); // prints "foo finished!""
}, 3000);
There is also a good example resource in Deno docs.
You can do that with the run like this:
// myscript.js
Deno.run({
cmd: ["echo", "hello world"]
})
You'll have to --allow-run when running the script in order for this to work:
deno run --allow-run ./myscript.js
If your shell command prints out some messages before the process is about to end, you really want pipe stdin and stdout to your own streams and also throw an exception which you can catch.
You can even alter the output while piping the process streams to your own streams:
async function run(cwd, ...cmd) {
const stdout = []
const stderr = []
cwd = cwd || Deno.cwd()
const p = Deno.run({
cmd,
cwd,
stdout: "piped",
stderr: "piped"
})
console.debug(`$ ${cmd.join(" ")}`)
const decoder = new TextDecoder()
streams.readableStreamFromReader(p.stdout).pipeTo(new WritableStream({
write(chunk) {
for (const line of decoder.decode(chunk).split(/\r?\n/)) {
stdout.push(line)
console.info(`[ ${cmd[0]} ] ${line}`)
}
},
}))
streams.readableStreamFromReader(p.stderr).pipeTo(new WritableStream({
write(chunk) {
for (const line of decoder.decode(chunk).split(/\r?\n/)) {
stderr.push(line)
console.error(`[ ${cmd[0]} ] ${line}`)
}
},
}))
const status = await p.status()
if (!status.success) {
throw new Error(`[ ${cmd[0]} ] failed with exit code ${status.code}`)
}
return {
status,
stdout,
stderr,
}
}
If you don't have different logic for each writable stream, you can also combine them to one:
streams.mergeReadableStreams(
streams.readableStreamFromReader(p.stdout),
streams.readableStreamFromReader(p.stderr),
).pipeTo(new WritableStream({
write(chunk): void {
for (const line of decoder.decode(chunk).split(/\r?\n/)) {
console.error(`[ ${cmd[0]} ] ${line}`)
}
},
}))
Alternatively, you can also invoke shell command via task runner such as drake as below
import { desc, run, task, sh } from "https://deno.land/x/drake#v1.5.0/mod.ts";
desc("Minimal Drake task");
task("hello", [], async function () {
console.log("Hello World!");
await sh("deno run --allow-env src/main.ts");
});
run();
$ deno run -A drakefile.ts hello
I want to create a queue of future tasks, with a key to avoid to add to the queue the same task already added.
This is my scenario:
async call to url1
async call to url1
async call to url2
What I need is:
add call to url1 to the queue and execute it
DO NOT add the second call to url1 to the queue and throw away it
add call to url2 to the queue, wait for call to url1 to be finished, and execute it
I read about StreamQueue and Queue, but I do not know if it fits for my needs.
Another structure I read about is await for with stream.
So I tried with something like:
await for (var url in stream) { // <--- I do not know how create this stream
var url = Uri.https('www.example.com', url);
List<dynamic> tmpItems;
try {
http.Response res = await http.get(url);
final data = json.decode(res.body);
tmpItems = _parseItems(data["data"]);
} catch(e) {
print(e);
}
if (this.mounted) {
return setState(() {
_items.addAll(tmpItems ?? []);
});
}
}
I do not know howto create stream and add to call to it.
ok first you need to have a urlArray that have the urls then implement an async method called getUrl as an example and implement it to get the data from the url this method takes the url as a parameter
Future getData(url,index) async {
//here you can call the function and handle the output(return value) as result
getUrl(url).then((result) {
// print(result);
//here check if index is smaller than the array length-1
//if true then call getData function again to get next url
if(index<urlArray.length)
{
--index;
getData(urlArray[index],index);
}
});
}
so the first call will be something like that getData(urlArray[0],0)
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.
I'm trying to process an array of items, and need to use an async function within the block. I need to make the outer loop some type of async function so I can use await within the loop.
But I can't seem to make the loop 'await' before continuing.
The code below just prints out msgs as [] and then runs through the loop.
// with a foreach
let msgs = [];
items.forEach(function(item, index) {
item.laws.forEach(async function(law) {
let orig = await Laws.coll.findOne({url: law.cname});
let title = Laws.title(orig);
let msg = `<${law.cname}|${title}>`;
msgs.push(msg);
debug("title", title, msg);
});
});
debug("msgs", msgs); // => shows []
and I also tried various syntax with map
items.map(await async function(item) {
item.laws.map(await async function(law) {
let orig = await Laws.coll.findOne({url: law.cname});
let title = Laws.title(orig);
let msg = `<${law.cname}|${title}>`;
msgs.push(msg);
debug("title", title, msg);
});
});
FWIW I tried:
await item.laws.map(async function() ...
//and
item.laws.map(await async function() ...
without success.
In both cases the loop runs after the debug("msgs", msgs) has printed.
There are two ways to solve the problem.
Use for(let of items){...} instead
Made promise sequences as described in Javascript Promises -> Creating a sequence
The reason is both map and forEach calls the provided callback but do not wait for it to be executed.
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'));
}