|
|
@@ -10,6 +10,7 @@
|
|
|
#include <optional>
|
|
|
#include <sstream>
|
|
|
#include <iomanip>
|
|
|
+#include <thread>
|
|
|
|
|
|
namespace smartbotic::webserver::api {
|
|
|
|
|
|
@@ -85,6 +86,9 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
|
|
|
// which the method-to-node-type mapping below cannot express, so it is
|
|
|
// matched first.
|
|
|
nlohmann::json form_trigger_data;
|
|
|
+ std::string form_response_mode = "immediate";
|
|
|
+ std::string form_config_title;
|
|
|
+ std::string form_response_message = "Thanks, your answer was received.";
|
|
|
auto form_node = findFormNode(workflow);
|
|
|
if (form_node.has_value()) {
|
|
|
const auto form_config = form_node->value("config", nlohmann::json::object());
|
|
|
@@ -108,6 +112,9 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
|
|
|
return;
|
|
|
}
|
|
|
form_trigger_data = form_data; // consumed below, in place of the JSON body
|
|
|
+ form_response_mode = form_config.value("responseMode", "immediate");
|
|
|
+ form_config_title = form_config.value("title", "");
|
|
|
+ form_response_message = form_config.value("responseMessage", form_response_message);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -219,8 +226,62 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
|
|
|
// Fired by the outside world, so it runs the published version.
|
|
|
grpc_req.set_use_published(true);
|
|
|
grpc_req.set_trigger_data(trigger_data.dump());
|
|
|
- grpc_req.set_wait_for_completion(true); // Wait for webhook response
|
|
|
- grpc_req.set_timeout_ms(30000); // 30 second timeout
|
|
|
+ // A form set to answer immediately must not hold the browser for the run.
|
|
|
+ // The slot claimed above is still released correctly: the runner reports
|
|
|
+ // execution.completed/failed/cancelled/waiting to
|
|
|
+ // /api/v1/internal/execution-event, and execution_controller.cpp:632
|
|
|
+ // releases it there - the same path fire-and-forget scheduled dispatch
|
|
|
+ // relies on today.
|
|
|
+ const bool wait_for_run = form_trigger_data.is_null() || form_response_mode != "immediate";
|
|
|
+ grpc_req.set_wait_for_completion(wait_for_run);
|
|
|
+ grpc_req.set_timeout_ms(wait_for_run ? 30000 : 0); // 30 second timeout when waiting
|
|
|
+
|
|
|
+ if (!wait_for_run) {
|
|
|
+ // ExecuteWorkflow is a plain synchronous RPC: the server call runs the
|
|
|
+ // workflow to completion (or until the client's deadline lapses) before
|
|
|
+ // it returns anything, regardless of wait_for_completion - that field
|
|
|
+ // only controls whether the response carries a result. Holding this
|
|
|
+ // handler's thread on that call would hold the browser for the run no
|
|
|
+ // matter how the request is configured, which is exactly what
|
|
|
+ // "immediate" promises not to do. So the call is moved to a detached
|
|
|
+ // thread: the browser gets the thank-you page right away, and the run
|
|
|
+ // proceeds in the background. The scheduler slot claimed above is
|
|
|
+ // still released correctly, the same way a scheduled dispatch's is -
|
|
|
+ // via the runner's execution.completed/failed/cancelled/waiting
|
|
|
+ // report to /api/v1/internal/execution-event.
|
|
|
+ std::thread([this, channel, workflow_id, execution_id, grpc_req]() {
|
|
|
+ auto bg_stub = proto::RunnerService::NewStub(channel);
|
|
|
+ proto::ExecuteWorkflowResponse bg_res;
|
|
|
+ grpc::ClientContext bg_ctx;
|
|
|
+ // Nobody is waiting on this response, so the deadline only needs
|
|
|
+ // to bound a runner that never answers at all.
|
|
|
+ bg_ctx.set_deadline(std::chrono::system_clock::now() + std::chrono::hours(1));
|
|
|
+
|
|
|
+ auto bg_status = bg_stub->ExecuteWorkflow(&bg_ctx, grpc_req, &bg_res);
|
|
|
+
|
|
|
+ if (!bg_status.ok()) {
|
|
|
+ if (bg_status.error_code() != grpc::StatusCode::DEADLINE_EXCEEDED) {
|
|
|
+ scheduler_.notifyExecutionFinished(workflow_id, execution_id);
|
|
|
+ }
|
|
|
+ LOG_ERROR("Immediate-mode webhook execution failed for workflow {}: {}",
|
|
|
+ workflow_id, bg_status.error_message());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bg_res.execution_id() != execution_id) {
|
|
|
+ LOG_WARN("Immediate-mode webhook run came back as {} but was dispatched as {} - "
|
|
|
+ "moving the schedule slot",
|
|
|
+ bg_res.execution_id(), execution_id);
|
|
|
+ scheduler_.notifyExecutionFinished(workflow_id, execution_id);
|
|
|
+ scheduler_.notifyExecutionStarted(workflow_id, bg_res.execution_id());
|
|
|
+ }
|
|
|
+ }).detach();
|
|
|
+
|
|
|
+ res.status = 200;
|
|
|
+ res.set_content(form_renderer::renderMessage(form_config_title, form_response_message),
|
|
|
+ "text/html; charset=utf-8");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
proto::ExecuteWorkflowResponse grpc_res;
|
|
|
grpc::ClientContext grpc_ctx;
|