Skip to content

Conversation

@heaths
Copy link
Member

@heaths heaths commented Nov 22, 2025

Resolves #3294

@github-actions github-actions bot added the Azure.Core The azure_core crate label Nov 22, 2025
@heaths
Copy link
Member Author

heaths commented Nov 25, 2025

This is a draft to get some earlier feedback. For now I only changed PageIterator since it was simpler - ItemIterator needs to do some extra things - and because one or the other pager has to change first to store the function instead of the stream. PageIterator is the "leaf" in this scenario, so into_pages() and any tests that rely on it were commented out. Module tests were updated, but no examples or other crates' tests have been updated yet.

I'll likely copy the code to ItemIterator as well and get rid of its from_callback since the type declaration will be complex. I'll define a pager::from_callback or something that will return an impl <Trait> where I want to keep the ItemIterator and PageIterator as trait names. Unfortunately, that also means getting rid of Pager because types can't alias traits currently, not without being dyn; though, to just get the stream maybe that's not so bad?

I'll also copy the implementation to Poller. While I will try to find a way to refactor the shared code for pagers, I probably won't for Poller because there's enough difference that it's probably not worth it and will likely have to diverge in the future anyway. Over-abstracting likely isn't worth it.

@heaths
Copy link
Member Author

heaths commented Nov 26, 2025

I can't make this work. The previous BoxedStream solution erased most of the type parameters, but by exposing all those types now, it forces us to use dynamic dispatch which limits a lot of use cases and puts owness on the callers. The goal was eliminate locking but given the network IO, I don't think it's worth the regression to the DX.

@heaths
Copy link
Member Author

heaths commented Nov 26, 2025

Actually, if the type params are the source of "ugliness" and complexity, then the goal is to eliminate them. If I use fewer in the declaration, I think can limit it to P: Page and C: AsRef<str> - the latter because most implements do/will use Url for a "next link" instead of some arbitrary String for an opaque continuation token, but we know the latter exists in Azure.

So, it might mean that Pager gains an extra type param - that part of the implementation leaks out into the API - but we can at least default that as well e.g.,

pub type Pager<P, F = JsonFormat, C = Url> = ItemIterator<Response<P, F>, C>;

Cosmos uses a String but also defines their own type alias anyway, so they could do something like:

pub type FeedPager<P, F = JsonFormat, C = String> = ItemIterator<Response<P, F>, C>;

Copy link
Member

@analogrelay analogrelay left a comment

Choose a reason for hiding this comment

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

This feels on-track to me. I have some comments that might allow us to reduce clone()s, but I'm not really worried about them.

I do think the C: AsRef<str> bound could be removed, or simplified to C: Debug (for tracing purposes). The Pager doesn't actually care that it's a string. It just flows it back and forth to the actual request function.

let mut iter = self.iter;

// Start with the current page until after all items are iterated.
iter.continuation_token = self.continuation_token.clone();
Copy link
Member

Choose a reason for hiding this comment

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

We should be able to move the continuation_token rather than cloning it, no?

continuation_token: Option<String>,
next_token: Arc<Mutex<Option<String>>>,
iter: PageIterator<P, C>,
continuation_token: Option<C>,
Copy link
Member

Choose a reason for hiding this comment

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

Does this actually need to hold the continuation_token itself? Or could the continuation_token functions just call in to PageIterator to get the current value?

Comment on lines +617 to +618
continuation_token: Option<C>,
options: PagerOptions<'static, C>,
Copy link
Member

Choose a reason for hiding this comment

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

We could just hold the PagerOptions, and update the continuation token there, to avoid cloning. Or, pull the entire PagerOptions apart in the constructor and only store the things we need here.

enum State<P, C: AsRef<str>> {
Init,
Pending(BoxedFuture<P, C>),
More(C),
Copy link
Member

Choose a reason for hiding this comment

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

We probably don't need to store a copy of the continuation on the State anymore. We can just pull it from the fields on PageIterator and pass &C in to the make_request func.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, though you might hit issues because the make_request future is probably hanging on to it. This is probably fine then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure.Core The azure_core crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eliminating locking and improve safety with Pagers, Pollers

3 participants