cln-plugin: Handle --help invocations better

We now have ternary outcomes for `Builder.configure()` and
`Builder.start()`:

 - Ok(Some(p)) means we were configured correctly, and can continue
   with our work normally
 - Ok(None) means that `lightningd` was invoked with `--help`, we
   weren't configured (which is not an error since the `lightningd` just
   implicitly told us to shut down) and user code should clean up and
   exit as well
 - Err(e) something went wrong, user code may report an error and exit.
This commit is contained in:
Christian Decker 2022-04-10 16:56:13 +02:00 committed by Rusty Russell
parent c36cef08bc
commit b359a24772
3 changed files with 41 additions and 13 deletions

View file

@ -6,7 +6,7 @@ use cln_plugin::{options, Builder, Error, Plugin};
use tokio; use tokio;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), anyhow::Error> { async fn main() -> Result<(), anyhow::Error> {
let plugin = Builder::new((), tokio::io::stdin(), tokio::io::stdout()) if let Some(plugin) = Builder::new((), tokio::io::stdin(), tokio::io::stdout())
.option(options::ConfigOption::new( .option(options::ConfigOption::new(
"test-option", "test-option",
options::Value::Integer(42), options::Value::Integer(42),
@ -16,8 +16,12 @@ async fn main() -> Result<(), anyhow::Error> {
.subscribe("connect", connect_handler) .subscribe("connect", connect_handler)
.hook("peer_connected", peer_connected_handler) .hook("peer_connected", peer_connected_handler)
.start() .start()
.await?; .await?
plugin.join().await {
plugin.join().await
} else {
Ok(())
}
} }
async fn testmethod(_p: Plugin<()>, _v: serde_json::Value) -> Result<serde_json::Value, Error> { async fn testmethod(_p: Plugin<()>, _v: serde_json::Value) -> Result<serde_json::Value, Error> {
@ -29,7 +33,10 @@ async fn connect_handler(_p: Plugin<()>, v: serde_json::Value) -> Result<(), Err
Ok(()) Ok(())
} }
async fn peer_connected_handler(_p: Plugin<()>, v: serde_json::Value) -> Result<serde_json::Value, Error> { async fn peer_connected_handler(
_p: Plugin<()>,
v: serde_json::Value,
) -> Result<serde_json::Value, Error> {
log::info!("Got a connect hook call: {}", v); log::info!("Got a connect hook call: {}", v);
Ok(json!({"result": "continue"})) Ok(json!({"result": "continue"}))
} }

View file

@ -28,19 +28,25 @@ async fn main() -> Result<()> {
ca_cert, ca_cert,
}; };
let plugin = Builder::new(state.clone(), tokio::io::stdin(), tokio::io::stdout()) let plugin = match Builder::new(state.clone(), tokio::io::stdin(), tokio::io::stdout())
.option(options::ConfigOption::new( .option(options::ConfigOption::new(
"grpc-port", "grpc-port",
options::Value::Integer(-1), options::Value::Integer(-1),
"Which port should the grpc plugin listen for incoming connections?", "Which port should the grpc plugin listen for incoming connections?",
)) ))
.configure() .configure()
.await?; .await?
{
Some(p) => p,
None => return Ok(()),
};
let bind_port = match plugin.option("grpc-port") { let bind_port = match plugin.option("grpc-port") {
Some(options::Value::Integer(-1)) => { Some(options::Value::Integer(-1)) => {
log::info!("`grpc-port` option is not configured, exiting."); log::info!("`grpc-port` option is not configured, exiting.");
plugin.disable("`grpc-port` option is not configured.").await?; plugin
.disable("`grpc-port` option is not configured.")
.await?;
return Ok(()); return Ok(());
} }
Some(options::Value::Integer(i)) => i, Some(options::Value::Integer(i)) => i,

View file

@ -139,7 +139,13 @@ where
self self
} }
pub async fn configure(mut self) -> Result<ConfiguredPlugin<S, I, O>, anyhow::Error> { /// Communicate with `lightningd` to tell it about our options,
/// RPC methods and subscribe to hooks, and then process the
/// initialization, configuring the plugin.
///
/// Returns `None` if we were invoked with `--help` and thus
/// should exit after this handshake
pub async fn configure(mut self) -> Result<Option<ConfiguredPlugin<S, I, O>>, anyhow::Error> {
let mut input = FramedRead::new(self.input.take().unwrap(), JsonRpcCodec::default()); let mut input = FramedRead::new(self.input.take().unwrap(), JsonRpcCodec::default());
// Sadly we need to wrap the output in a mutex in order to // Sadly we need to wrap the output in a mutex in order to
@ -189,7 +195,7 @@ where
// If we are being called with --help we will get // If we are being called with --help we will get
// disconnected here. That's expected, so don't // disconnected here. That's expected, so don't
// complain about it. // complain about it.
0 return Ok(None);
} }
}; };
@ -213,7 +219,7 @@ where
// Leave the `init` reply pending, so we can disable based on // Leave the `init` reply pending, so we can disable based on
// the options if required. // the options if required.
Ok(ConfiguredPlugin { Ok(Some(ConfiguredPlugin {
// The JSON-RPC `id` field so we can reply correctly. // The JSON-RPC `id` field so we can reply correctly.
init_id, init_id,
input, input,
@ -228,7 +234,7 @@ where
), ),
}, },
plugin, plugin,
}) }))
} }
/// Build and start the plugin loop. This performs the handshake /// Build and start the plugin loop. This performs the handshake
@ -236,8 +242,17 @@ where
/// Core Lightning and dispatches them to the handlers. It only /// Core Lightning and dispatches them to the handlers. It only
/// returns after completing the handshake to ensure that the /// returns after completing the handshake to ensure that the
/// configuration and initialization was successfull. /// configuration and initialization was successfull.
pub async fn start(self) -> Result<Plugin<S>, anyhow::Error> { ///
self.configure().await?.start().await /// If `lightningd` was called with `--help` we won't get a
/// `Plugin` instance and return `None` instead. This signals that
/// we should exit, and not continue running. `start()` returns in
/// order to allow user code to perform cleanup if necessary.
pub async fn start(self) -> Result<Option<Plugin<S>>, anyhow::Error> {
if let Some(cp) = self.configure().await? {
Ok(Some(cp.start().await?))
} else {
Ok(None)
}
} }
fn handle_get_manifest( fn handle_get_manifest(