~autumnull/haredo-devel

Use signals to propagate failures v1 APPLIED

Ember Sawady: 1
 Use signals to propagate failures

 2 files changed, 42 insertions(+), 13 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~autumnull/haredo-devel/patches/38766/mbox | git am -3
Learn more about email & git

[PATCH] Use signals to propagate failures Export this patch

---
test.do fix kinda hacky but shouldn't matter in real usage
 src/main.ha | 51 ++++++++++++++++++++++++++++++++++++++++-----------
 test.do     |  4 ++--
 2 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/src/main.ha b/src/main.ha
index ad74a51..851acf6 100644
--- a/src/main.ha
+++ b/src/main.ha
@@ -14,6 +14,7 @@ use temp;
use time;
use types;
use unix;
use unix::signal;
use uuid;

type context = struct {
@@ -21,15 +22,17 @@ type context = struct {
	quiet: bool,
	colorless: bool,
	indent: str,
	toplevel: bool,
	parent_timestamp: time::instant,
	parent: (exec::process | void),
	jobs: uint,
	rd: io::file,
	wr: io::file,
};
let tmpdir: str = "";

def OLD: time::instant = time::instant {
let tmpdir = "";
let failed = false;

def OLD = time::instant {
	sec = types::I64_MIN,
	nsec = 0,
};
@@ -47,7 +50,13 @@ const envprogs: [_](str, str) = [
	("SCDOC", "scdoc"),
];

fn handler(sig: int, info: *signal::siginfo, ucontext: *void) void = {
	failed = true;
};

export fn main() void = {
	signal::handle(signal::SIGUSR1, &handler);

	// job slots for the -j option are handled by reading a byte from a pipe when
	// a slot is acquired, and writing a byte to the pipe when a slot is freed.
	let (rd, wr) = match (os::getenv("HAREDO_PIPE")) {
@@ -69,7 +78,11 @@ export fn main() void = {
		quiet = os::getenv("HAREDO_QUIET") is str,
		colorless = os::getenv("HAREDO_COLORLESS") is str,
		indent = os::tryenv("HAREDO_INDENT", ""),
		toplevel = parent is void,
		parent = match (os::getenv("HAREDO_PID")) {
		case void => void;
		case let s: str =>
			yield strconv::stoi(s)!;
		},
		parent_timestamp = if (parent is void) {
			yield OLD;
		} else {
@@ -123,7 +136,7 @@ export fn main() void = {
		free(children);
	};

	if (ctx.toplevel) {
	if (ctx.parent is void) {
		// initialize job slots
		for (let i = 0z; i < ctx.jobs - 1; i += 1) {
			io::write(ctx.wr, [0])!;
@@ -141,13 +154,15 @@ export fn main() void = {
			yield child;
		case let e: exec::error =>
			log(&ctx, lvl::ERR, cmd.args[i], exec::strerror(e))!;
			os::exit(1);
			fail(&ctx);
		};
		if (ctx.jobs > 1) {
			append(children, (child.0, child.1, cmd.args[i]));
		} else {
			// if there's only one job (or zero), wait immediately for the child
			const status = exec::wait(&child.0)!;
			let status = exec::wait(&child.0);
			for (status is errors::interrupted; status = exec::wait(&child.0)) void;
			const status = status!;
			defer io::write(ctx.wr, [0])!;
			match (cleanup_child(&ctx, &status, child.1, cmd.args[i])) {
			case let new: bool =>
@@ -155,14 +170,16 @@ export fn main() void = {
				log(&ctx, lvl::OK, cmd.args[i], msg)!;
			case let err: !str =>
				log(&ctx, lvl::ERR, cmd.args[i], err)!;
				os::exit(1);
				fail(&ctx);
			};
		};
	};

	// ...then wait for them to finish...
	for (len(children) != 0) {
		const (child, status) = exec::waitany()!;
		let w = exec::waitany();
		for (w is errors::interrupted; w = exec::waitany()) void;
		const (child, status) = w!;
		let i = 0z;
		for (i < len(children); i += 1) {
			if (child == children[i].0) break;
@@ -179,10 +196,12 @@ export fn main() void = {
			log(&ctx, lvl::OK, cmd.args[i], msg)!;
		case let err: !str =>
			log(&ctx, lvl::ERR, cmd.args[i], err)!;
			os::exit(1);
			fail(&ctx);
		};
	};

	if (failed) fail(&ctx);

	// ...then check for updates
	for (let i = 0z; i < len(cmd.args); i += 1) {
		if (cmd.args[i] == "++") continue;
@@ -198,6 +217,15 @@ export fn main() void = {
	os::exit(1);
};

@noreturn fn fail(ctx: *context) void = {
	match (ctx.parent) {
	case void => void;
	case let p: exec::process =>
		exec::kill(p, exec::signal::SIGUSR1): void;
	};
	os::exit(1);
};

@fini fn exit() void = {
	os::rmdirall(tmpdir): void;
};
@@ -251,13 +279,14 @@ fn try_do(
	const timestamp = fmt::asprintf("{},{}", timestamp.sec, timestamp.nsec);
	defer free(timestamp);
	exec::setenv(&cmd, "HAREDO_PARENT", timestamp)?;
	exec::setenv(&cmd, "HAREDO_PID", strconv::itos(exec::self()))?;

	// pass runtime environment variables down to child process
	if (ctx.verbose) exec::setenv(&cmd, "HAREDO_VERBOSE", "1")?;
	if (ctx.quiet) exec::setenv(&cmd, "HAREDO_QUIET", "1")?;
	if (ctx.colorless) exec::setenv(&cmd, "HAREDO_COLORLESS", "1")?;

	if (ctx.toplevel) {
	if (ctx.parent is void) {
		// set default program variables in toplevel process
		for (let i = 0z; i < len(envprogs); i += 1) {
			const (var, value) = envprogs[i];
diff --git a/test.do b/test.do
index 0a511e0..46bbb9b 100644
--- a/test.do
+++ b/test.do
@@ -6,5 +6,5 @@ touch test/rv64/all.h
haredo test/all
haredo test/clean-gen

haredo test/should-fail || true
haredo test/unwriteable || true
env -u HAREDO_PID haredo test/should-fail || true
env -u HAREDO_PID haredo test/unwriteable || true
-- 
2.39.1
Thanks!

~Autumn