22
33import java .time .Duration ;
44import java .util .concurrent .CompletableFuture ;
5- import java .util .concurrent .Executors ;
6- import java .util .concurrent .ScheduledExecutorService ;
5+ import java .util .concurrent .CompletionStage ;
76import java .util .concurrent .TimeUnit ;
87
98import org .slf4j .Logger ;
109import org .slf4j .LoggerFactory ;
1110
1211import ai .docling .serve .api .DoclingServeConvertApi ;
12+ import ai .docling .serve .api .DoclingServeTaskApi ;
1313import ai .docling .serve .api .convert .request .ConvertDocumentRequest ;
1414import ai .docling .serve .api .convert .response .ConvertDocumentResponse ;
1515import ai .docling .serve .api .task .request .TaskResultRequest ;
1616import ai .docling .serve .api .task .request .TaskStatusPollRequest ;
17- import ai .docling .serve .api .task .response .TaskStatus ;
1817import ai .docling .serve .api .task .response .TaskStatusPollResponse ;
1918import ai .docling .serve .api .util .ValidationUtils ;
2019
@@ -26,22 +25,22 @@ public final class ConvertOperations implements DoclingServeConvertApi {
2625 private static final Logger LOG = LoggerFactory .getLogger (ConvertOperations .class );
2726
2827 private final HttpOperations httpOperations ;
29- private final TaskOperations taskOperations ;
28+ private final DoclingServeTaskApi taskApi ;
3029 private final Duration asyncPollInterval ;
3130 private final Duration asyncTimeout ;
3231
3332 /**
3433 * Creates a new ConvertOperations instance.
3534 *
3635 * @param httpOperations the HTTP operations handler for executing requests
37- * @param taskOperations the task operations handler for polling and retrieving results
36+ * @param taskApi the task operations handler for polling and retrieving results
3837 * @param asyncPollInterval the interval between status polls for async operations
3938 * @param asyncTimeout the maximum time to wait for async operations to complete
4039 */
41- public ConvertOperations (HttpOperations httpOperations , TaskOperations taskOperations ,
40+ public ConvertOperations (HttpOperations httpOperations , DoclingServeTaskApi taskApi ,
4241 Duration asyncPollInterval , Duration asyncTimeout ) {
4342 this .httpOperations = httpOperations ;
44- this .taskOperations = taskOperations ;
43+ this .taskApi = taskApi ;
4544 this .asyncPollInterval = asyncPollInterval ;
4645 this .asyncTimeout = asyncTimeout ;
4746 }
@@ -68,85 +67,69 @@ private <I> RequestContext<I, TaskStatusPollResponse> createAsyncRequestContext(
6867 .build ();
6968 }
7069
71- @ Override
70+ @ Override
7271 public CompletableFuture <ConvertDocumentResponse > convertSourceAsync (ConvertDocumentRequest request ) {
7372 ValidationUtils .ensureNotNull (request , "request" );
7473
75- CompletableFuture <ConvertDocumentResponse > resultFuture = new CompletableFuture <>();
76-
77- ScheduledExecutorService scheduler = Executors .newSingleThreadScheduledExecutor (r -> {
78- Thread t = new Thread (r , "docling-async-poller" );
79- t .setDaemon (true );
80- return t ;
81- });
82-
83- // Start the async conversion
84- CompletableFuture .supplyAsync (() ->
74+ // Start the async conversion and chain the polling logic
75+ return CompletableFuture .supplyAsync (() ->
8576 this .httpOperations .executePost (createAsyncRequestContext ("/v1/convert/source/async" , request ))
86- ).thenAccept (taskResponse -> {
77+ ).thenCompose (taskResponse -> {
8778 var taskId = taskResponse .getTaskId ();
8879 LOG .info ("Started async conversion with task ID: {}" , taskId );
8980
9081 long startTime = System .currentTimeMillis ();
91-
92- // Schedule the polling task
93- Runnable pollTask = new Runnable () {
94- @ Override
95- public void run () {
96- try {
97- // Check if we've timed out
98- if (System .currentTimeMillis () - startTime > asyncTimeout .toMillis ()) {
99- resultFuture .completeExceptionally (
100- new RuntimeException ("Async conversion timed out after " + asyncTimeout + " for task: " + taskId ));
101- scheduler .shutdown ();
102- return ;
103- }
104-
105- // Use the existing pollTaskStatus method from TaskOperations
106- var pollRequest = TaskStatusPollRequest .builder ().taskId (taskId ).build ();
107- var statusResponse = taskOperations .pollTaskStatus (pollRequest );
108- var status = statusResponse .getTaskStatus ();
109-
110- LOG .debug ("Task {} status: {}" , taskId , status );
111-
112- if (status == TaskStatus .SUCCESS ) {
113- LOG .info ("Task {} completed successfully" , taskId );
114- // Use the existing convertTaskResult method from TaskOperations
115- var resultRequest = TaskResultRequest .builder ().taskId (taskId ).build ();
116- try {
117- var result = taskOperations .convertTaskResult (resultRequest );
118- resultFuture .complete (result );
119- } catch (Exception e ) {
120- resultFuture .completeExceptionally (e );
121- }
122- scheduler .shutdown ();
123- } else if (status == TaskStatus .FAILURE ) {
124- String errorMessage = "Task failed" ;
125- if (statusResponse .getTaskStatusMetadata () != null ) {
126- errorMessage = "Task failed: " + statusResponse .getTaskStatusMetadata ();
127- }
128- resultFuture .completeExceptionally (
129- new RuntimeException ("Async conversion failed for task " + taskId + ": " + errorMessage ));
130- scheduler .shutdown ();
131- } else {
132- // Still in progress (PENDING or STARTED), schedule next poll
133- scheduler .schedule (this , asyncPollInterval .toMillis (), TimeUnit .MILLISECONDS );
134- }
135- } catch (Exception e ) {
136- resultFuture .completeExceptionally (e );
137- scheduler .shutdown ();
138- }
139- }
140- };
141-
142- // Start polling immediately
143- scheduler .schedule (pollTask , 0 , TimeUnit .MILLISECONDS );
144- }).exceptionally (e -> {
145- resultFuture .completeExceptionally (e );
146- scheduler .shutdown ();
147- return null ;
82+ return pollTaskUntilComplete (taskId , startTime );
14883 });
84+ }
14985
150- return resultFuture ;
86+ /**
87+ * Recursively polls a task until it completes, fails, or times out.
88+ *
89+ * @param taskId the ID of the task to poll
90+ * @param startTime the timestamp when polling started (for timeout calculation)
91+ * @return a CompletableFuture that completes with the conversion result
92+ */
93+ private CompletableFuture <ConvertDocumentResponse > pollTaskUntilComplete (String taskId , long startTime ) {
94+ // Check if we've timed out
95+ if (System .currentTimeMillis () - startTime > asyncTimeout .toMillis ()) {
96+ return CompletableFuture .failedFuture (
97+ new RuntimeException ("Async conversion timed out after " + asyncTimeout + " for task: " + taskId ));
98+ }
99+
100+ // Poll the task status
101+ var pollRequest = TaskStatusPollRequest .builder ().taskId (taskId ).build ();
102+
103+ return CompletableFuture .supplyAsync (() -> taskApi .pollTaskStatus (pollRequest ))
104+ .thenCompose (statusResponse -> pollTaskStatus (statusResponse , startTime ));
105+ }
106+
107+ private CompletionStage <ConvertDocumentResponse > pollTaskStatus (TaskStatusPollResponse statusResponse , long startTime ) {
108+ var status = statusResponse .getTaskStatus ();
109+ var taskId = statusResponse .getTaskId ();
110+ LOG .debug ("Task {} status: {}" , taskId , status );
111+
112+ return switch (statusResponse .getTaskStatus ()) {
113+ case SUCCESS -> {
114+ LOG .info ("Task {} completed successfully" , taskId );
115+ // Retrieve the result
116+ var resultRequest = TaskResultRequest .builder ().taskId (taskId ).build ();
117+ yield CompletableFuture .supplyAsync (() -> taskApi .convertTaskResult (resultRequest ));
118+ }
119+ case FAILURE -> {
120+ var errorMessage = "Task failed" ;
121+ if (statusResponse .getTaskStatusMetadata ()!=null ) {
122+ errorMessage = "Task failed: " + statusResponse .getTaskStatusMetadata ();
123+ }
124+ yield CompletableFuture .failedFuture (
125+ new RuntimeException ("Async conversion failed for task " + taskId + ": " + errorMessage ));
126+ }
127+ default ->
128+ // Still in progress (PENDING or STARTED), schedule next poll after delay
129+ CompletableFuture .supplyAsync (
130+ () -> null ,
131+ CompletableFuture .delayedExecutor (asyncPollInterval .toMillis (), TimeUnit .MILLISECONDS )
132+ ).thenCompose (v -> pollTaskUntilComplete (taskId , startTime ));
133+ };
151134 }
152135}
0 commit comments