Repository: electron/electron
Pull request: fix: `utilityProcess` pid should be `undefined` after exit

#### Description of Change

Refs https://github.com/electron/electron/pull/44243.

The fix for the above surfaced a new problem, which is that the `pid` of the `child` process was still defined when the `exit` event is emitted. This worked previously as a fluke of the duplicate events - the [handle](https://github.com/electron/electron/blob/122685194a5642b73367e823aadf3942b0290099/lib/browser/api/utility-process.ts#L99) was reset _after_ exit was emitted the first time and so would be undefined in the callback the second time. We need to ensure it's reset on process termination.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] relevant documentation, tutorials, templates and examples are changed or added
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed an issue where a `utilityProcess` pid would not be `undefined` after exit.

Changed files:
filename: docs/api/utility-process.md
patch: @@ -92,6 +92,8 @@ the child process exits, then the value is `undefined` after the `exit` event is
 ```js
 const child = utilityProcess.fork(path.join(__dirname, 'test.js'))
 
+console.log(child.pid) // undefined
+
 child.on('spawn', () => {
   console.log(child.pid) // Integer
 })
@@ -101,6 +103,8 @@ child.on('exit', () => {
 })
 ```
 
+**Note:** You can use the `pid` to determine if the process is currently running.
+
 #### `child.stdout`
 
 A `NodeJS.ReadableStream | null` that represents the child process's stdout.

filename: shell/browser/api/electron_api_utility_process.cc
patch: @@ -255,6 +255,8 @@ void UtilityProcessWrapper::HandleTermination(uint64_t exit_code) {
 
   if (pid_ != base::kNullProcessId)
     GetAllUtilityProcessWrappers().Remove(pid_);
+
+  pid_ = base::kNullProcessId;
   CloseConnectorPort();
   EmitWithoutEvent("exit", exit_code);
   Unpin();

filename: spec/api-utility-process-spec.ts
patch: @@ -240,13 +240,31 @@ describe('utilityProcess module', () => {
     it('is valid when child process launches successfully', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
       await once(child, 'spawn');
-      expect(child.pid).to.not.be.null();
+      expect(child).to.have.property('pid').that.is.a('number');
     });
 
     it('is undefined when child process fails to launch', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'does-not-exist.js'));
       expect(child.pid).to.be.undefined();
     });
+
+    it('is undefined before the child process is spawned succesfully', async () => {
+      const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
+      expect(child.pid).to.be.undefined();
+      await once(child, 'spawn');
+      child.kill();
+    });
+
+    it('is undefined when child process is killed', async () => {
+      const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
+      await once(child, 'spawn');
+
+      expect(child).to.have.property('pid').that.is.a('number');
+      expect(child.kill()).to.be.true();
+
+      await once(child, 'exit');
+      expect(child.pid).to.be.undefined();
+    });
   });
 
   describe('stdout property', () => {