google-cloud-rust icon indicating copy to clipboard operation
google-cloud-rust copied to clipboard

Consider a way to get the operation name from the poller

Open coryan opened this issue 8 months ago • 2 comments

When we create a poller, such as in:

https://github.com/googleapis/google-cloud-rust/blob/9f00dffe46fe5d50d9f7e3181c7e65d85be4ac8d/guide/samples/src/lro.rs#L118-L139

One may want to cancel the polling operation after a few iterations, or otherwise save it for future use. We need a way to get the operation name, and maybe even a way to start a poller given the operation name (and the types).

For this issue we need to determine if such a change would require a breaking change or not. If it does not require a breaking change, we save the design and we can remove this issue from the milestone. If we do need a breaking change then we need to fix it.

coryan avatar Apr 26 '25 17:04 coryan

I just prototyped a solution and it does not require changing any APIs. I am removing this from the stabilization milestone.

coryan avatar May 08 '25 16:05 coryan

While no breaking changes are needed, I cannot get this to work without larger changes to *-wkt. I am going to save the main ideas so we can do this later.

Background

The basic idea is to extend the Poller trait to include a suspend(self) method. The implementation looks like this:

    fn suspend(self) -> Option<super::PollerSnapshot<ResponseType, MetadataType>> {
        self.operation.map(|name| super::PollerSnapshot::new(name))
    }

As you would expect, the PollerSnapshot holds the name of the operation and the types:

#[derive(Clone, Debug, PartialEq)]
pub struct PollerSnapshot<R, M> {
    name: String,
    response: PhantomData<R>,
    metadata: PhantomData<M>,
}

We would want to implement a resume_poller function in the client. Returning a builder (so we can add polling options):

    pub fn resume_poller<R, M>(
        &self,
        snapshot: lro::PollerSnapshot<R, M>,
    ) -> super::builder::workflows::ResumePoller<R, M>

The builder would provide a resume() function:

        pub fn resume(self) -> impl lro::Poller<R, M> {
           // ... skip some stuff ...

            lro::internal::new_poller(polling_error_policy, polling_backoff_policy, start, query)
        }

Problem

The issue is working with unit types. To call new_poller() you need R and M to satisfy a number of bounds:

            R: wkt::message::Message
                + serde::ser::Serialize
                + serde::de::DeserializeOwned
                + Send
                + Sync
                + 'static,

But () does not implement wkt::message::Message. I am not sure I want to introduce that implementation. And no amount of trickery will allow us to inject a new trait implementation generic on T: wkt::message::Message and also on ().

Useful ideas for any future implementation

The implementation of resume() uses GetOperation() for both start and query:

        pub fn resume(self) -> impl lro::Poller<R, M> {

            let stub = self.inner.stub.clone();
            let mut options = self.inner.options.clone();
            options.set_retry_policy(gax::retry_policy::NeverRetry);
            let query = move |name| {
                let stub = stub.clone();
                let options = options.clone();
                async {
                    let op = GetOperation::new(stub)
                        .set_name(name)
                        .with_options(options)
                        .send()
                        .await?;
                    Ok(lro::internal::Operation::<R, M>::new(op))
                }
            };

            let inner = query.clone();
            let snapshot = self.snapshot;
            let start = move || {
                let name = snapshot.name().to_string();
                let q = inner.clone();
                async move { q(name).await }
            };

            let polling_error_policy = self
                .inner
                .stub
                .get_polling_error_policy(&self.inner.options);
            let polling_backoff_policy = self
                .inner
                .stub
                .get_polling_backoff_policy(&self.inner.options);

            lro::internal::new_poller(polling_error_policy, polling_backoff_policy, start, query)
        }

coryan avatar May 09 '25 13:05 coryan