Skip to content
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

feat: Add Deno.exit.code support #23609

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open

Conversation

lukeed
Copy link

@lukeed lukeed commented Apr 29, 2024

Related to #23605

This pull request introduces the ability to set a would-be exit code for the Deno process without forcing an immediate exit, through the new Deno.exit.code API.

  • Implements Deno.exit.code getter and setter: Adds support for setting and retrieving a would-be exit code via Deno.exit.code. This allows for asynchronous cleanup before process termination without immediately exiting.
  • Ensures type safety: The setter for Deno.exit.code validates that the provided value is a number, throwing a TypeError if not, to ensure that only valid exit codes are set.
  • Integrates with existing exit logic: The setter for Deno.exit.code utilizes the op_set_exit_code operation to correctly register the intended exit code within Deno's internal mechanisms.
  • Adds comprehensive unit tests: New tests in cli/tests/unit/os_test.ts verify the functionality of the Deno.exit.code getter and setter, including type validation and the behavior that setting the exit code does not cause an immediate process exit.

For more details, open the Copilot Workspace session.

@lukeed lukeed mentioned this pull request Apr 30, 2024
@lukeed lukeed marked this pull request as ready for review April 30, 2024 00:25
@lucacasonato
Copy link
Member

There is no test that ensures that the process actually exits with the given code.

@lukeed lukeed changed the title Add Deno.exit.code support feat: Add Deno.exit.code support Apr 30, 2024
@lukeed
Copy link
Author

lukeed commented May 2, 2024

I'm not sure if the lint error has anything to do with this PR? It claims to be unhappy with 01_core.js which wasn't modified in this PR

@bartlomieju
Copy link
Member

@lukeed it's actually complaining about a missing copyright:

Copyright header is missing: /home/runner/work/deno/deno/cli/tests/unit/os_test.ts (incorrect line is '// This file contains unit tests for the Deno.exit.code API')

@bartlomieju bartlomieju added this to the 1.44 milestone May 2, 2024
@bartlomieju
Copy link
Member

Also I think it would be really good to add one integration test that checks that the set exit code is actually used when the process exits - could you look into that by following one of the existing tests in tests/specs/run/?

@Seally
Copy link

Seally commented May 2, 2024

Should Deno.exit.code always return the same value as process.exitCode? process.exitCode also stores its own exit code as a JS-side variable, so as far as I can tell at least one of them will be wrong once either setter is called.

get exitCode() {
return globalProcessExitCode;
}

@bartlomieju
Copy link
Member

Should Deno.exit.code always return the same value as process.exitCode? process.exitCode also stores its own exit code as a JS-side variable, so as far as I can tell at least one of them will be wrong once either setter is called.

get exitCode() {
return globalProcessExitCode;
}

Thanks for pointing this out, they should share the same value actually.

@lukeed let me know if you need any help with this PR, it might be more involved since we should share the same code between Deno.exit.code and Node compat layer. I'll be happy to help.

@bartlomieju
Copy link
Member

@lukeed we discussed this API during the team meeting and the broad agreement was that Deno.exitCode would be a better choice. Is there a particular reason why you abandoned the previous approach?

@lukeed
Copy link
Author

lukeed commented May 7, 2024

All good, I'll defer to you all :)

In order to get Deno.exitCode to work, it has to be a getter and a setter. However, the only way to do that is by converting the entirety of 30_os.js to use export default so that the top-level getter/setter pair can be defined.

-- export {
++ const os = {
  env,
  execPath,
  exit,
++ get exitCode() { ... },
++ set exitCode(value) { ... },
  gid,
  hostname,
  loadavg,
  networkInterfaces,
  osRelease,
  osUptime,
  setExitHandler,
  systemMemoryInfo,
  uid,
};

++ export default os;

I abandoned that approach because (i assumed) it would mess up the NS builder since everything else is using named exports.

@bartlomieju
Copy link
Member

bartlomieju commented May 7, 2024

Got it, thanks for the explainer. I'll take a deeper look at it this week, maybe we can figure something out :)

EDIT: Actually taking a quick look - I think it would work if we exported getExitCode and setExitCode from 30_os.js and then in 90_deno_ns.js we added get exitCode() and set exitCode() to denoNs object as you describe above. It appears we'll need to add op_get_exit_code to make sure both "Deno" native and "Node" native APIs see the same code properly, but that seems easy enough. WDYT @lukeed?

@lukeed
Copy link
Author

lukeed commented May 8, 2024

Ok, I reverted to getter/setter pair & linked process.exit/Code too.

TBH, between rust-analyzer, ts server, the 106k files w/ system scanning, my computer literally grinds to a halt :/ I wait about 5s per keystroke lolsob. So if anything else is needed, someone else will have to carry the torch 🙇

Aside from not knowing where to put Node process tests, the only issue I can see is this:

function demo() {
  process.exitCode = '123'; 
  process.exitCode; // -> "123" (unfortunately)
  // then, for some reason
  Deno.exitCode = 1;
  process.exitCode; //-> "123" (does not see Deno update)
  process.exitCode = "123";
 //-> process exits w/ 123
}

It gets weird if you're using both Deno & Node APIs at the same time. But choosing one camp always has Deno exiting correctly.

The only ways I can see this being fixed are:

  1. the Deno.exitCode setter calls into process.exitCode
    but this is bad because it perma-links the Node polyfill runtime w/ Deno namespace

  2. make the Deno NS behave like Node & hold/return the raw value until it actually exits.
    That would mean not calling op_set_exit_code until Deno.exit() and Deno.exitCode would have to be able to return string | number | null | undefined like Node does. (yuck)

Basically, Node will parse non-nullish values to see if they're NaN and, if so, throw a TypeError. But then it always returns the raw value.

@bartlomieju
Copy link
Member

Thanks for the update @lukeed, sorry to hear your problems with rust-analyzer. I would suggest you add a config to it that uses --feature __runtime_js_sources - this should save some time on each change as it would have to do a lot less work. I can take over the PR and finish it.

Aside from not knowing where to put Node process tests, the only issue I can see is this:

It gets weird if you're using both Deno & Node APIs at the same time. But choosing one camp always has Deno exiting correctly.

The only ways I can see this being fixed are:

the Deno.exitCode setter calls into process.exitCode
but this is bad because it perma-links the Node polyfill runtime w/ Deno namespace

make the Deno NS behave like Node & hold/return the raw value until it actually exits.
That would mean not calling op_set_exit_code until Deno.exit() and Deno.exitCode would have to be able to return string | number | null | undefined like Node does. (yuck)

Thanks for pointing this out, I think I know how to handle that correctly. Point 1 is not really a concern since that's already the case 🫠 For point 2 I think for Deno API we can try to coerce the value to a number and also throw a TypeError if it can't be parsed. So Deno.exitCode: number | undefined.

I'll make sure this lands in v1.44.

@lukeed
Copy link
Author

lukeed commented May 9, 2024

we can try to coerce the value to a number and also throw a TypeError if it can't be parsed

This already happens :) I was pointing out that if you wanted to have perfect synchronization with Node, you'd have to change Deno side to allow for more than number|undefined to be the return type.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants