nushell/crates/nu-plugin-engine/src/persistent.rs

323 lines
11 KiB
Rust
Raw Normal View History

Split the plugin crate (#12563) # Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib`
2024-04-27 19:08:12 +02:00
use crate::{
init::{create_command, make_plugin_interface},
PluginGc,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
};
Split the plugin crate (#12563) # Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib`
2024-04-27 19:08:12 +02:00
use super::{PluginInterface, PluginSource};
use nu_plugin_core::CommunicationMode;
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
use nu_protocol::{
engine::{EngineState, Stack},
PluginGcConfig, PluginIdentity, RegisteredPlugin, ShellError,
};
use std::{
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
collections::HashMap,
sync::{Arc, Mutex},
};
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
/// A box that can keep a plugin that was spawned persistent for further uses. The plugin may or
/// may not be currently running. [`.get()`] gets the currently running plugin, or spawns it if it's
/// not running.
#[derive(Debug)]
pub struct PersistentPlugin {
/// Identity (filename, shell, name) of the plugin
identity: PluginIdentity,
/// Mutable state
mutable: Mutex<MutableState>,
}
/// The mutable state for the persistent plugin. This should all be behind one lock to prevent lock
/// order problems.
#[derive(Debug)]
struct MutableState {
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
/// Reference to the plugin if running
running: Option<RunningPlugin>,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
/// Plugin's preferred communication mode (if known)
preferred_mode: Option<PreferredCommunicationMode>,
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
/// Garbage collector config
gc_config: PluginGcConfig,
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
#[derive(Debug, Clone, Copy)]
enum PreferredCommunicationMode {
Stdio,
#[cfg(feature = "local-socket")]
LocalSocket,
}
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
#[derive(Debug)]
struct RunningPlugin {
/// Interface (which can be cloned) to the running plugin
interface: PluginInterface,
/// Garbage collector for the plugin
gc: PluginGc,
}
impl PersistentPlugin {
/// Create a new persistent plugin. The plugin will not be spawned immediately.
pub fn new(identity: PluginIdentity, gc_config: PluginGcConfig) -> PersistentPlugin {
PersistentPlugin {
identity,
mutable: Mutex::new(MutableState {
running: None,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
preferred_mode: None,
gc_config,
}),
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
}
/// Get the plugin interface of the running plugin, or spawn it if it's not currently running.
///
/// Will call `envs` to get environment variables to spawn the plugin if the plugin needs to be
/// spawned.
Split the plugin crate (#12563) # Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib`
2024-04-27 19:08:12 +02:00
pub fn get(
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
self: Arc<Self>,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
envs: impl FnOnce() -> Result<HashMap<String, String>, ShellError>,
) -> Result<PluginInterface, ShellError> {
let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
msg: format!(
"plugin `{}` mutex poisoned, probably panic during spawn",
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
self.identity.name()
),
})?;
if let Some(ref running) = mutable.running {
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
// It exists, so just clone the interface
Ok(running.interface.clone())
} else {
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
// Try to spawn. On success, `mutable.running` should have been set to the new running
// plugin by `spawn()` so we just then need to clone the interface from there.
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
//
// We hold the lock the whole time to prevent others from trying to spawn and ending
// up with duplicate plugins
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
//
// TODO: We should probably store the envs somewhere, in case we have to launch without
// envs (e.g. from a custom value)
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
let envs = envs()?;
let result = self.clone().spawn(&envs, &mut mutable);
// Check if we were using an alternate communication mode and may need to fall back to
// stdio.
if result.is_err()
&& !matches!(
mutable.preferred_mode,
Some(PreferredCommunicationMode::Stdio)
)
{
log::warn!("{}: Trying again with stdio communication because mode {:?} failed with {result:?}",
self.identity.name(),
mutable.preferred_mode);
// Reset to stdio and try again, but this time don't catch any error
mutable.preferred_mode = Some(PreferredCommunicationMode::Stdio);
self.clone().spawn(&envs, &mut mutable)?;
}
Ok(mutable
.running
.as_ref()
.ok_or_else(|| ShellError::NushellFailed {
msg: "spawn() succeeded but didn't set interface".into(),
})?
.interface
.clone())
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
}
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
/// Run the plugin command, then set up and set `mutable.running` to the new running plugin.
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
fn spawn(
self: Arc<Self>,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
envs: &HashMap<String, String>,
mutable: &mut MutableState,
) -> Result<(), ShellError> {
// Make sure `running` is set to None to begin
if let Some(running) = mutable.running.take() {
// Stop the GC if there was a running plugin
running.gc.stop_tracking();
}
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
let source_file = self.identity.filename();
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
// Determine the mode to use based on the preferred mode
let mode = match mutable.preferred_mode {
// If not set, we try stdio first and then might retry if another mode is supported
Some(PreferredCommunicationMode::Stdio) | None => CommunicationMode::Stdio,
// Local socket only if enabled
#[cfg(feature = "local-socket")]
Some(PreferredCommunicationMode::LocalSocket) => {
CommunicationMode::local_socket(source_file)
}
};
let mut plugin_cmd = create_command(source_file, self.identity.shell(), &mode);
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
// We need the current environment variables for `python` based plugins
// Or we'll likely have a problem when a plugin is implemented in a virtual Python environment.
plugin_cmd.envs(envs);
let program_name = plugin_cmd.get_program().to_os_string().into_string();
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
// Before running the command, prepare communication
let comm = mode.serve()?;
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
// Run the plugin command
let child = plugin_cmd.spawn().map_err(|err| {
let error_msg = match err.kind() {
std::io::ErrorKind::NotFound => match program_name {
Ok(prog_name) => {
format!("Can't find {prog_name}, please make sure that {prog_name} is in PATH.")
}
_ => {
format!("Error spawning child process: {err}")
}
},
_ => {
format!("Error spawning child process: {err}")
}
};
ShellError::PluginFailedToLoad { msg: error_msg }
})?;
// Start the plugin garbage collector
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
let gc = PluginGc::new(mutable.gc_config.clone(), &self)?;
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
let pid = child.id();
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
let interface = make_plugin_interface(
child,
comm,
Arc::new(PluginSource::new(self.clone())),
Some(pid),
Some(gc.clone()),
)?;
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
// If our current preferred mode is None, check to see if the plugin might support another
// mode. If so, retry spawn() with that mode
#[cfg(feature = "local-socket")]
if mutable.preferred_mode.is_none()
&& interface
.protocol_info()?
Split the plugin crate (#12563) # Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib`
2024-04-27 19:08:12 +02:00
.supports_feature(&nu_plugin_protocol::Feature::LocalSocket)
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
{
log::trace!(
"{}: Attempting to upgrade to local socket mode",
self.identity.name()
);
// Stop the GC we just created from tracking so that we don't accidentally try to
// stop the new plugin
gc.stop_tracking();
// Set the mode and try again
mutable.preferred_mode = Some(PreferredCommunicationMode::LocalSocket);
return self.spawn(envs, mutable);
}
mutable.running = Some(RunningPlugin { interface, gc });
Ok(())
}
fn stop_internal(&self, reset: bool) -> Result<(), ShellError> {
let mut mutable = self.mutable.lock().map_err(|_| ShellError::NushellFailed {
msg: format!(
"plugin `{}` mutable mutex poisoned, probably panic during spawn",
self.identity.name()
),
})?;
// If the plugin is running, stop its GC, so that the GC doesn't accidentally try to stop
// a future plugin
if let Some(ref running) = mutable.running {
running.gc.stop_tracking();
}
// We don't try to kill the process or anything, we just drop the RunningPlugin. It should
// exit soon after
mutable.running = None;
// If this is a reset, we should also reset other learned attributes like preferred_mode
if reset {
mutable.preferred_mode = None;
}
Ok(())
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
}
impl RegisteredPlugin for PersistentPlugin {
fn identity(&self) -> &PluginIdentity {
&self.identity
}
fn is_running(&self) -> bool {
// If the lock is poisoned, we return false here. That may not be correct, but this is a
// failure state anyway that would be noticed at some point
self.mutable
.lock()
.map(|m| m.running.is_some())
.unwrap_or(false)
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
fn pid(&self) -> Option<u32> {
// Again, we return None for a poisoned lock.
self.mutable
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
.lock()
.ok()
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
.and_then(|r| r.running.as_ref().and_then(|r| r.interface.pid()))
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
fn stop(&self) -> Result<(), ShellError> {
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
self.stop_internal(false)
}
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
fn reset(&self) -> Result<(), ShellError> {
self.stop_internal(true)
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
fn set_gc_config(&self, gc_config: &PluginGcConfig) {
if let Ok(mut mutable) = self.mutable.lock() {
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
// Save the new config for future calls
mutable.gc_config = gc_config.clone();
// If the plugin is already running, propagate the config change to the running GC
if let Some(gc) = mutable.running.as_ref().map(|running| running.gc.clone()) {
// We don't want to get caught holding the lock
drop(mutable);
gc.set_config(gc_config.clone());
gc.flush();
Keep plugins persistently running in the background (#12064) # Description This PR uses the new plugin protocol to intelligently keep plugin processes running in the background for further plugin calls. Running plugins can be seen by running the new `plugin list` command, and stopped by running the new `plugin stop` command. This is an enhancement for the performance of plugins, as starting new plugin processes has overhead, especially for plugins in languages that take a significant amount of time on startup. It also enables plugins that have persistent state between commands, making the migration of features like dataframes and `stor` to plugins possible. Plugins are automatically stopped by the new plugin garbage collector, configurable with `$env.config.plugin_gc`: ```nushell $env.config.plugin_gc = { # Configuration for plugin garbage collection default: { enabled: true # true to enable stopping of inactive plugins stop_after: 10sec # how long to wait after a plugin is inactive to stop it } plugins: { # alternate configuration for specific plugins, by name, for example: # # gstat: { # enabled: false # } } } ``` If garbage collection is enabled, plugins will be stopped after `stop_after` passes after they were last active. Plugins are counted as inactive if they have no running plugin calls. Reading the stream from the response of a plugin call is still considered to be activity, but if a plugin holds on to a stream but the call ends without an active streaming response, it is not counted as active even if it is reading it. Plugins can explicitly disable the GC as appropriate with `engine.set_gc_disabled(true)`. The `version` command now lists plugin names rather than plugin commands. The list of plugin commands is accessible via `plugin list`. Recommend doing this together with #12029, because it will likely force plugin developers to do the right thing with mutability and lead to less unexpected behavior when running plugins nested / in parallel. # User-Facing Changes - new command: `plugin list` - new command: `plugin stop` - changed command: `version` (now lists plugin names, rather than commands) - new config: `$env.config.plugin_gc` - Plugins will keep running and be reused, at least for the configured GC period - Plugins that used mutable state in weird ways like `inc` did might misbehave until fixed - Plugins can disable GC if they need to - Had to change plugin signature to accept `&EngineInterface` so that the GC disable feature works. #12029 does this anyway, and I'm expecting (resolvable) conflicts with that # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` Because there is some specific OS behavior required for plugins to not respond to Ctrl-C directly, I've developed against and tested on both Linux and Windows to ensure that works properly. # After Submitting I think this probably needs to be in the book somewhere
2024-03-10 00:10:22 +01:00
}
}
}
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync> {
self
}
}
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
/// Anything that can produce a plugin interface.
pub trait GetPlugin: RegisteredPlugin {
/// Retrieve or spawn a [`PluginInterface`]. The `context` may be used for determining
/// environment variables to launch the plugin with.
fn get_plugin(
self: Arc<Self>,
context: Option<(&EngineState, &mut Stack)>,
) -> Result<PluginInterface, ShellError>;
}
impl GetPlugin for PersistentPlugin {
fn get_plugin(
self: Arc<Self>,
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
mut context: Option<(&EngineState, &mut Stack)>,
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
) -> Result<PluginInterface, ShellError> {
self.get(|| {
// Get envs from the context if provided.
let envs = context
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
.as_mut()
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
.map(|(engine_state, stack)| {
// We need the current environment variables for `python` based plugins. Or
// we'll likely have a problem when a plugin is implemented in a virtual Python
// environment.
let stack = &mut stack.start_capture();
nu_engine::env::env_to_strings(engine_state, stack)
})
.transpose()?;
Local socket mode and foreground terminal control for plugins (#12448) # Description Adds support for running plugins using local socket communication instead of stdio. This will be an optional thing that not all plugins have to support. This frees up stdio for use to make plugins that use stdio to create terminal UIs, cc @amtoine, @fdncred. This uses the [`interprocess`](https://crates.io/crates/interprocess) crate (298 stars, MIT license, actively maintained), which seems to be the best option for cross-platform local socket support in Rust. On Windows, a local socket name is provided. On Unixes, it's a path. The socket name is kept to a relatively small size because some operating systems have pretty strict limits on the whole path (~100 chars), so on macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash includes the plugin filename and timestamp to be unique enough. This also adds an API for moving plugins in and out of the foreground group, which is relevant for Unixes where direct terminal control depends on that. TODO: - [x] Generate local socket path according to OS conventions - [x] Add support for passing `--local-socket` to the plugin executable instead of `--stdio`, and communicating over that instead - [x] Test plugins that were broken, including [amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore) - [x] Automatically upgrade to using local sockets when supported, falling back if it doesn't work, transparently to the user without any visible error messages - Added protocol feature: `LocalSocket` - [x] Reset preferred mode to `None` on `register` - [x] Allow plugins to detect whether they're running on a local socket and can use stdio freely, so that TUI plugins can just produce an error message otherwise - Implemented via `EngineInterface::is_using_stdio()` - [x] Clean up foreground state when plugin command exits on the engine side too, not just whole plugin - [x] Make sure tests for failure cases work as intended - `nu_plugin_stress_internals` added # User-Facing Changes - TUI plugins work - Non-Rust plugins could optionally choose to use this - This might behave differently, so will need to test it carefully across different operating systems # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Document local socket option in plugin contrib docs - [ ] Document how to do a terminal UI plugin in plugin contrib docs - [ ] Document: `EnterForeground` engine call - [ ] Document: `LeaveForeground` engine call - [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
Ok(envs.unwrap_or_default())
Add test support crate for plugin developers (#12259) # Description Adds a `nu-plugin-test-support` crate with an interface that supports testing plugins. Unlike in reality, these plugins run in the same process on separate threads. This will allow testing aspects of the plugin internal state and handling serialized plugin custom values easily. We still serialize their custom values and all of the engine to plugin logic is still in play, so from a logical perspective this should still expose any bugs that would have been caused by that. The only difference is that it doesn't run in a different process, and doesn't try to serialize everything to the final wire format for stdin/stdout. TODO still: - [x] Clean up warnings about private types exposed in trait definition - [x] Automatically deserialize plugin custom values in the result so they can be inspected - [x] Automatic plugin examples test function - [x] Write a bit more documentation - [x] More tests - [x] Add MIT License file to new crate # User-Facing Changes Plugin developers get a nice way to test their plugins. # Tests + Formatting Run the tests with `cargo test -p nu-plugin-test-support -- --show-output` to see some examples of what the failing test output for examples can look like. I used the `difference` crate (MIT licensed) to make it look nice. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] Add a section to the book about testing - [ ] Test some of the example plugins this way - [ ] Add example tests to nu_plugin_template so plugin developers have something to start with
2024-03-23 19:29:54 +01:00
})
}
}