Skip to content

Commit defe5a5

Browse files
Support task timeout
V2.19
1 parent d38e5dd commit defe5a5

3 files changed

Lines changed: 128 additions & 24 deletions

File tree

include/runtime/runtime_facade.hpp

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <chrono>
34
#include <cstddef>
45
#include <future>
56
#include <optional>
@@ -40,6 +41,10 @@ struct InvokeResult {
4041
json raw_response = json::object();
4142
};
4243

44+
struct InvokeOptions {
45+
std::optional<std::chrono::milliseconds> timeout;
46+
};
47+
4348
template<bool EnableThreadSafety = false>
4449
class BasicRuntimeFacade {
4550
public:
@@ -72,32 +77,101 @@ class BasicRuntimeFacade {
7277
return describeSchema(adapter_.getToolSchemaJson(tool_name));
7378
}
7479

75-
InvokeResult invoke(InvokeRequest request)
76-
{
77-
return submitInvoke(std::move(request)).get();
78-
}
79-
80-
std::future<InvokeResult> submitInvoke(InvokeRequest request)
80+
InvokeResult invoke(InvokeRequest request, InvokeOptions options = {})
8181
{
8282
const std::string tool_name = request.tool_name;
8383

84+
std::optional<task_scheduler::TaskDeadline> deadline;
85+
if (options.timeout.has_value())
86+
{
87+
deadline = std::chrono::steady_clock::now() + *options.timeout;
88+
}
89+
8490
try
8591
{
86-
auto response_future = scheduler_.submitRequest(makeRequestJson(std::move(request)));
87-
return std::async(std::launch::deferred, [tool_name, response_future = std::move(response_future)]() mutable {
88-
try
89-
{
90-
return normalizeResponse(tool_name, response_future.get());
91-
}
92-
catch (const json_invoke::JsonInvokeError& e)
92+
auto response_future = scheduler_.submitRequest(makeRequestJson(std::move(request)),
93+
std::move(deadline));
94+
95+
if (options.timeout.has_value())
96+
{
97+
if (response_future.wait_for(*options.timeout) == std::future_status::timeout)
9398
{
94-
return makeErrorResult(tool_name, e.code(), e.what());
99+
return makeErrorResult(tool_name, "timeout",
100+
"tool invocation exceeded timeout of " +
101+
std::to_string(options.timeout->count()) + "ms");
95102
}
96-
catch (const std::exception& e)
103+
}
104+
105+
try
106+
{
107+
return normalizeResponse(tool_name, response_future.get());
108+
}
109+
catch (const std::runtime_error& e)
110+
{
111+
if (std::string(e.what()) == "task_timeout")
97112
{
98-
return makeErrorResult(tool_name, "unknown_error", e.what());
113+
return makeErrorResult(tool_name, "timeout",
114+
"tool invocation timed out in queue");
99115
}
100-
});
116+
return makeErrorResult(tool_name, "unknown_error", e.what());
117+
}
118+
catch (const json_invoke::JsonInvokeError& e)
119+
{
120+
return makeErrorResult(tool_name, e.code(), e.what());
121+
}
122+
catch (const std::exception& e)
123+
{
124+
return makeErrorResult(tool_name, "unknown_error", e.what());
125+
}
126+
}
127+
catch (const json_invoke::JsonInvokeError& e)
128+
{
129+
return makeErrorResult(tool_name, e.code(), e.what());
130+
}
131+
catch (const std::exception& e)
132+
{
133+
return makeErrorResult(tool_name, "unknown_error", e.what());
134+
}
135+
}
136+
137+
std::future<InvokeResult> submitInvoke(InvokeRequest request, InvokeOptions options = {})
138+
{
139+
const std::string tool_name = request.tool_name;
140+
141+
std::optional<task_scheduler::TaskDeadline> deadline;
142+
if (options.timeout.has_value())
143+
{
144+
deadline = std::chrono::steady_clock::now() + *options.timeout;
145+
}
146+
147+
try
148+
{
149+
auto response_future = scheduler_.submitRequest(makeRequestJson(std::move(request)),
150+
std::move(deadline));
151+
return std::async(std::launch::deferred,
152+
[tool_name, response_future = std::move(response_future)]() mutable {
153+
try
154+
{
155+
return normalizeResponse(tool_name, response_future.get());
156+
}
157+
catch (const std::runtime_error& e)
158+
{
159+
if (std::string(e.what()) == "task_timeout")
160+
{
161+
return makeErrorResult(tool_name, "timeout",
162+
"tool invocation timed out in queue");
163+
}
164+
return makeErrorResult(tool_name, "unknown_error", e.what());
165+
}
166+
catch (const json_invoke::JsonInvokeError& e)
167+
{
168+
return makeErrorResult(tool_name, e.code(), e.what());
169+
}
170+
catch (const std::exception& e)
171+
{
172+
return makeErrorResult(tool_name, "unknown_error", e.what());
173+
}
174+
});
101175
}
102176
catch (const json_invoke::JsonInvokeError& e)
103177
{

include/task_scheduler/task_scheduler.hpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <algorithm>
4+
#include <chrono>
45
#include <condition_variable>
56
#include <cstddef>
67
#include <deque>
@@ -9,6 +10,7 @@
910
#include <future>
1011
#include <memory>
1112
#include <mutex>
13+
#include <optional>
1214
#include <stdexcept>
1315
#include <thread>
1416
#include <unordered_map>
@@ -21,9 +23,12 @@ namespace task_scheduler {
2123

2224
using ScheduledTaskFunction = std::function<json()>;
2325

26+
using TaskDeadline = std::chrono::steady_clock::time_point;
27+
2428
struct ScheduledTask {
2529
TaskExecutionPlan plan;
2630
ScheduledTaskFunction run;
31+
std::optional<TaskDeadline> deadline;
2732
};
2833

2934
class ITaskScheduler {
@@ -123,13 +128,22 @@ class KeyedTaskScheduler final : public ITaskScheduler {
123128
pending_tasks_.erase(ready_it);
124129
}
125130

126-
try
131+
if (item.task.deadline.has_value() &&
132+
std::chrono::steady_clock::now() > *item.task.deadline)
127133
{
128-
item.promise.set_value(item.task.run());
134+
item.promise.set_exception(std::make_exception_ptr(
135+
std::runtime_error("task_timeout")));
129136
}
130-
catch (...)
137+
else
131138
{
132-
item.promise.set_exception(std::current_exception());
139+
try
140+
{
141+
item.promise.set_value(item.task.run());
142+
}
143+
catch (...)
144+
{
145+
item.promise.set_exception(std::current_exception());
146+
}
133147
}
134148

135149
{
@@ -312,11 +326,13 @@ class KeyedTaskScheduler final : public ITaskScheduler {
312326
};
313327

314328
template<typename Fn>
315-
ScheduledTask makeScheduledTask(TaskExecutionPlan plan, Fn&& fn)
329+
ScheduledTask makeScheduledTask(TaskExecutionPlan plan, Fn&& fn,
330+
std::optional<TaskDeadline> deadline = std::nullopt)
316331
{
317332
return ScheduledTask{
318333
std::move(plan),
319334
ScheduledTaskFunction(std::forward<Fn>(fn)),
335+
std::move(deadline),
320336
};
321337
}
322338

include/task_scheduler/task_scheduler_facade.hpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#pragma once
22

3+
#include <chrono>
34
#include <exception>
45
#include <functional>
56
#include <future>
7+
#include <optional>
68
#include <utility>
79

810
#include <task_scheduler/task_scheduler.hpp>
@@ -38,10 +40,21 @@ class BasicTaskSchedulerFacade {
3840

3941
std::future<json> submitRequest(json request)
4042
{
41-
return submitRequest(std::move(request), RequestExecutionObserver{});
43+
return submitRequest(std::move(request), RequestExecutionObserver{}, std::nullopt);
4244
}
4345

4446
std::future<json> submitRequest(json request, RequestExecutionObserver observer)
47+
{
48+
return submitRequest(std::move(request), std::move(observer), std::nullopt);
49+
}
50+
51+
std::future<json> submitRequest(json request, std::optional<TaskDeadline> deadline)
52+
{
53+
return submitRequest(std::move(request), RequestExecutionObserver{}, std::move(deadline));
54+
}
55+
56+
std::future<json> submitRequest(json request, RequestExecutionObserver observer,
57+
std::optional<TaskDeadline> deadline)
4558
{
4659
auto plan = classifier_.classify(request);
4760
if (observer.on_classified)
@@ -74,7 +87,8 @@ class BasicTaskSchedulerFacade {
7487
}
7588
throw;
7689
}
77-
}));
90+
},
91+
std::move(deadline)));
7892
}
7993

8094
private:

0 commit comments

Comments
 (0)