Skip to content

Conversation

@justfalter
Copy link

This PR started with a desire to add a summary status bar, but I quickly found some improvements were required to command handling to make that happen.

  • Improves visibility into the state of the extension by adding a Run On Save Status status bar. Text of running Sync: 3 Async: 2 indicates that the extension is currently running 3 synchronous and 2 async commands. Text of idle Sync: 0 Async: 0 indicates that nothing is currently running.
    • Clicking the status bar reveals the Run On Save output.
  • Removes vestigial status bar code that stopped working a long time ago.
  • Improves synchronous command handling:
    • Sync commands are processed one at a time in FIFO order. Prior to this change, saving 20 files at the same time (like with a search/replace) would lead to 20 separate sync commands being run simultaneously. With this change, the 20 commands would be executed one at a time.
  • When disabled, terminates all running commands. This provides a much-needed means to terminate commands when things have gone awry.
    • The signal used to kill the processes is controlled via command's killSignal property, defaulting to SIGTERM.

Description of Changes

Summarize changes this PR introduces.

Testing

Poke around with the status bar.

Have run on save config, verify that the status bar displays expected information.

Verify disabling extension terminates running commands.

The following command will spawn sync and async commands, each starting a "sleep":

{
  "emeraldwalk.runonsave": {
    "commands": [
      {
        "match": ".*",
        "cmd": "sleep 99999"
      },
      {
        "match": ".*",
        "cmd": "sleep 88888",
        "isAsync": true
      }
    ]
  }
}
  1. Save several files
  2. Observe that the number of sync and async commands have counted upward.
  3. run ps aux| grep sleep and observe the accumulation of a sleep 99999 sync command and several sleep 88888 async commands.
  4. Run the Run On Save: Disable command.
  5. Observe that the processes have been terminated and the queue cleared.

Checklist

  • Did you update any relevant docs / samples?
  • Did you include details on how to test your changes?

This PR started with a desire to add a summary status bar, but I quickly found
some improvements were required to command handling to make that happen.

- Improves visibility into the state of the extension by adding a
  `Run On Save Status` status bar. Text of  `running Sync: 3 Async: 2`
  indicates that the extension is currently running 3 synchronous and 2 async
  commands. Text of `idle Sync: 0 Async: 0` indicates that nothing is currently
  running.
    - Clicking the status bar reveals the Run On Save output.
- Removes vestigial status bar code that stopped working a long time ago.
- Improves synchronous command handling:
  - Sync commands are processed one at a time in FIFO order. Prior to
    this change, saving 20 files at the same time (like with a search/replace)
    would lead to 20 separate sync commands being run
    simultaneously. With this change, the 20 commands would be executed
    one at a time.
- When disabled, terminates all running commands. This provides a
  much-needed means to terminate commands when things have gone awry.
  - The signal used to kill the processes is controlled via
    command's `killSignal` property, defaulting to `SIGTERM`.
@justfalter
Copy link
Author

justfalter commented Nov 12, 2025

@bmingles My team makes heavy use of the run on save extension, and I'd really appreciate your feedback! Thank you very much!

@bmingles
Copy link
Member

@justfalter Sorry for the delay. Lots of competing priorities. I'll try to take a look at the changes sometime this week when I can properly test.

@justfalter
Copy link
Author

@justfalter Sorry for the delay. Lots of competing priorities. I'll try to take a look at the changes sometime this week when I can properly test.

Thank you, @bmingles! I appreciate any time that you can give!

Copy link
Member

@bmingles bmingles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justfalter I've read through the diff and left a few comments.

I still need to actually test and make sure I fully understand the new queueing mechanism.

I also specifically want to test in Windows to make sure there are no surprises with the kill signal configurations since not all are supported by Windows.

Thanks again for the PR. I'm glad to have real users fixing real problems they are experiencing.

src/extension.ts Outdated
}

class RunOnSaveExtension {
interface runCommandConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface should go in the model.ts file. Also, minor thing, but convention for types / interfaces is PascalCase

src/extension.ts Outdated
});
}

public disable(): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also make this one async

"error"
]
},
"killSignal": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to test on Windows and make sure there aren't any footguns here since not all kill signals are supported there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node's documentation for kill makes no mention of what it does for Windows platforms, but a look at Node's source code reveals that it will unconditionally terminate a process when SIGQUIT, SIGTERM, SIGINT, or SIGKILL are provided. Attempts to send any other signal appears to result in an error.

const atLeastOneCommandIsRunning = unfinishedSyncCount !== 0 || asyncCount !== 0;

if (!this.isEnabled()) {
// If we are disabled but have unfinished commands, then we are "draining".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the scenario that can result in "draining"? Since commands are killed when extension is disabled, is there any meaningful time that we would see this status?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The draining state simply reflects the knowledge that it can take time for a process to fully exit after receiving a termination signal. It's entirely possible that a process ignores a SIGTERM, so the draining state would allow the user to be aware of that instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah makes sense. In that case, I'm good with the state, but I think I'd like to name it something a little more generic like "stopping" since I think "draining" may not be as broadly recognized.

Copy link
Member

@bmingles bmingles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justfalter First of all, thank you for your contribution. I appreciate the help in improving the extension.

Inline Comments

I've done some testing and reviewed the changes a bit closer. I've left a handful of inline comments in the code.

Command Management Changes

Regarding the command management changes, there are 2 primary concerns I have related to changes in existing functionality.

  1. Parallel commands (isAsync:true) that are configured after sequential ones (isAsync:false) used to wait until the preceeding sequential command completed. It seems that the parallel commands execute immediately now. They should wait for prior sequential commands to finish. For reference, I used this configuration to test:
"emeraldwalk.runonsave": {
      // Messages to show before & after all commands
      "message": "*** All Start ***",
      "messageAfter": "*** All Complete ***",
      // Show elappsed time for all commands
      "showElapsed": true,
      "commands": [
        {
          // This cmd should block all following commands (sequential and parallel ones)
          "match": ".*",
          "cmd": "sleep 3",
          "message": "1. sleep 3"
        },
        {
          // This cmd should wait until the first finishes
          "match": ".*",
          "cmd": "sleep 10",
          "message": "2. sleep 10",
          "isAsync": true
        },
        {
          "match": ".*",
          "cmd": "sleep 4",
          "message": "3. sleep 4",
          "isAsync": true
        }
}
  1. I think your observation that saving multiple files can cause many exec commands to fire at once is a good one and something I did not account for. My concern with the current changes is that it may impact existing users that are expecting the pre-existing behavior. For example, a user may actually want 20 exec commands to fire off in parallel across multiple files. Changing them to run sequentially across files could introduce unexpected lag in certain cases. I think it's a good fix, but it should be opt-in via a setting of some sort.

This does introduce some additional complexity since the command queue triggered by each file save will need to honor the rule mentioned in bullet 1 but also be able to order sequential commands across files if a user opts in to this feature.

Thanks again for the PR. Let me know if you need any clarification on my comments.

@justfalter
Copy link
Author

@justfalter First of all, thank you for your contribution. I appreciate the help in improving the extension.

Inline Comments

I've done some testing and reviewed the changes a bit closer. I've left a handful of inline comments in the code.

Command Management Changes

Regarding the command management changes, there are 2 primary concerns I have related to changes in existing functionality.

  1. Parallel commands (isAsync:true) that are configured after sequential ones (isAsync:false) used to wait until the preceeding sequential command completed. It seems that the parallel commands execute immediately now. They should wait for prior sequential commands to finish. For reference, I used this configuration to test:

You're completely right -- I'm not sure how/why I came to the conclusion that the async commands would fire immediately.
I'll work on rectifying this in my PR.

  1. I think your observation that saving multiple files can cause many exec commands to fire at once is a good one and something I did not account for. My concern with the current changes is that it may impact existing users that are expecting the pre-existing behavior. For example, a user may actually want 20 exec commands to fire off in parallel across multiple files. Changing them to run sequentially across files could introduce unexpected lag in certain cases. I think it's a good fix, but it should be opt-in via a setting of some sort.

This does introduce some additional complexity since the command queue triggered by each file save will need to honor the rule mentioned in bullet 1 but also be able to order sequential commands across files if a user opts in to this feature.

A global option to control the behavior would be fair. The only difference between the two modes would be the use of a single queue vs one per save event... I'll probably have to extract the queue out of the extension class...

Thanks again for the PR. Let me know if you need any clarification on my comments.

Gladly. Everything you're saying so far makes sense to me!

- Match prior behavior of only launching async commands when encountered
  in config order. We do not wait for an async command to complete
  before starting the next command.
@bmingles
Copy link
Member

@justfalter FYI, I merged a couple PRs that will require a rebase or merge down

  • prettier is now installed and checked as part of PRs to main. This is likely to be the source of most of the branch conflicts. You might be able to make this easier by running npx prettier src --write before attempting to rebase / merge down, but I'm not sure.
  • there are now some basic tests that verify the sequencing of commands npm test. This will hopefully make it a little bit easier to confirm the changes you make don't change existing sequencing

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.

2 participants