+sub cat_async_begin {
+ my ($self) = @_;
+ cleanup($self) if alternates_changed($self);
+ batch_prepare($self);
+ die 'BUG: already in async' if $self->{inflight};
+ $self->{inflight} = [];
+}
+
+sub cat_async ($$$;$) {
+ my ($self, $oid, $cb, $arg) = @_;
+ my $inflight = $self->{inflight} // cat_async_begin($self);
+ if (scalar(@$inflight) >= MAX_INFLIGHT) {
+ cat_async_step($self, $inflight);
+ }
+
+ print { $self->{out} } $oid, "\n" or fail($self, "write error: $!");
+ push(@$inflight, $oid, $cb, $arg);
+}
+
+# this is safe to call inside $cb, but not guaranteed to enqueue
+# returns true if successful, undef if not.
+sub async_prefetch {
+ my ($self, $oid, $cb, $arg) = @_;
+ if (defined($self->{async_cat}) && (my $inflight = $self->{inflight})) {
+ # we could use MAX_INFLIGHT here w/o the halving,
+ # but lets not allow one client to monopolize a git process
+ if (scalar(@$inflight) < int(MAX_INFLIGHT/2)) {
+ print { $self->{out} } $oid, "\n" or
+ fail($self, "write error: $!");
+ return push(@$inflight, $oid, $cb, $arg);
+ }
+ }
+ undef;
+}
+
+sub extract_cmt_time {
+ my ($bref, undef, undef, undef, $modified) = @_;
+
+ if ($$bref =~ /^committer .*?> ([0-9]+) [\+\-]?[0-9]+/sm) {
+ my $cmt_time = $1 + 0;
+ $$modified = $cmt_time if $cmt_time > $$modified;
+ }