Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 100 additions & 55 deletions components/spider-execution-manager/src/client/grpc/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use async_trait::async_trait;
use spider_core::types::id::{ExecutionManagerId, SessionId};
use spider_proto_rust::storage::{
self,
execution_manager_liveness_error,
execution_manager_liveness_service_client::ExecutionManagerLivenessServiceClient,
register_execution_manager_response,
update_execution_manager_heartbeat_response,
};
use tonic::transport::{Channel, Endpoint};
use tonic::{
Code,
transport::{Channel, Endpoint},
};

use crate::client::liveness::{LivenessClient, LivenessResponseError, RegistrationResponse};

Expand Down Expand Up @@ -52,7 +52,7 @@ impl LivenessClient for GrpcLivenessClient {
.clone()
.register_execution_manager(request)
.await
.map_err(to_transport_error)?
.map_err(|status| map_liveness_status(&status))?
.into_inner();

register_response_to_result(response)
Expand All @@ -70,28 +70,28 @@ impl LivenessClient for GrpcLivenessClient {
.clone()
.update_execution_manager_heartbeat(request)
.await
.map_err(to_transport_error)?
.map_err(|status| map_liveness_status(&status))?
.into_inner();

heartbeat_response_to_result(response)
}
}

impl From<storage::ExecutionManagerLivenessError> for LivenessResponseError {
fn from(error: storage::ExecutionManagerLivenessError) -> Self {
match execution_manager_liveness_error::ErrCode::try_from(error.err_code) {
Ok(execution_manager_liveness_error::ErrCode::MarkedDead) => Self::MarkedDead,
Ok(execution_manager_liveness_error::ErrCode::InvalidInput) => {
Self::IllegalId(error.message)
}
Ok(
execution_manager_liveness_error::ErrCode::Server
| execution_manager_liveness_error::ErrCode::Unspecified,
) => Self::Transport(error.message),
Err(error) => Self::Transport(format!(
"unknown execution manager liveness error kind: {error}"
)),
}
/// Maps a [`tonic::Status`] returned by an execution-manager-liveness RPC into a
/// [`LivenessResponseError`].
///
/// # Returns
///
/// * [`LivenessResponseError::MarkedDead`] when storage has already reaped the execution manager,
/// signalled by `FAILED_PRECONDITION`.
/// * [`LivenessResponseError::IllegalId`] when storage rejects the execution manager id, signalled
/// by `INVALID_ARGUMENT`.
/// * [`LivenessResponseError::Transport`] for any other failure.
fn map_liveness_status(status: &tonic::Status) -> LivenessResponseError {
match status.code() {
Code::FailedPrecondition => LivenessResponseError::MarkedDead,
Code::InvalidArgument => LivenessResponseError::IllegalId(status.message().to_owned()),
_ => LivenessResponseError::Transport(status.message().to_owned()),
}
}

Expand All @@ -102,16 +102,20 @@ impl From<storage::ExecutionManagerLivenessError> for LivenessResponseError {
fn register_response_to_result(
response: storage::RegisterExecutionManagerResponse,
) -> Result<RegistrationResponse, LivenessResponseError> {
match response.result {
Some(register_execution_manager_response::Result::Registration(registration)) => {
match response.registration {
Some(registration) => {
if registration.session_id == 0 {
return Err(LivenessResponseError::Transport(
"register execution manager response carried a zero session id".to_owned(),
));
}
Ok(RegistrationResponse {
em_id: ExecutionManagerId::from(registration.execution_manager_id),
session_id: registration.session_id,
Comment on lines 112 to 114

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify whether ExecutionManagerId permits zero and whether storage can emit zero.
# Expectation: If ExecutionManagerId is non-zero/reserved-zero, add a zero guard before Line 113.

set -euo pipefail

echo "ExecutionManagerId definition and constructors:"
rg -n -C4 'ExecutionManagerId|struct .*ExecutionManager|type .*ExecutionManager|impl .*ExecutionManager' components/spider-core components 2>/dev/null || true

echo
echo "ExecutionManagerRegistration construction sites:"
rg -n -C4 'ExecutionManagerRegistration|execution_manager_id:' components 2>/dev/null || true

Repository: y-scope/spider

Length of output: 50371


Add a zero-value guard for execution_manager_id

The session_id is guarded against zero, but execution_manager_id is converted directly without validation. The storage layer and system protocol require valid ExecutionManagerId values to be non-zero (as enforced by tests in components/spider-storage/src/grpc.rs), where zero serves as a sentinel for null or error states. A direct conversion without a check risks propagating an invalid identifier into the runtime state.

Context
            if registration.session_id == 0 {
                return Err(LivenessResponseError::Transport(
                    "register execution manager response carried a zero session id".to_owned(),
                ));
            }
+           if registration.execution_manager_id == 0 {
+               return Err(LivenessResponseError::Transport(
+                   "register execution manager response carried a zero execution manager id".to_owned(),
+               ));
+           }
            Ok(RegistrationResponse {
                em_id: ExecutionManagerId::from(registration.execution_manager_id),
                session_id: registration.session_id,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Ok(RegistrationResponse {
em_id: ExecutionManagerId::from(registration.execution_manager_id),
session_id: registration.session_id,
if registration.session_id == 0 {
return Err(LivenessResponseError::Transport(
"register execution manager response carried a zero session id".to_owned(),
));
}
if registration.execution_manager_id == 0 {
return Err(LivenessResponseError::Transport(
"register execution manager response carried a zero execution manager id".to_owned(),
));
}
Ok(RegistrationResponse {
em_id: ExecutionManagerId::from(registration.execution_manager_id),
session_id: registration.session_id,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/spider-execution-manager/src/client/grpc/liveness.rs` around lines
112 - 114, Add a zero-value guard for execution_manager_id in the registration
handling path so it is validated before constructing the RegistrationResponse.
In the liveness client logic where RegistrationResponse is built, mirror the
existing session_id check by rejecting or handling a zero execution_manager_id
before calling ExecutionManagerId::from(registration.execution_manager_id),
ensuring invalid sentinel values do not enter runtime state.

})
}
Some(register_execution_manager_response::Result::Error(error)) => Err(error.into()),
None => Err(LivenessResponseError::Transport(
"register execution manager response missing result".to_owned(),
"register execution manager response missing registration".to_owned(),
)),
}
}
Expand All @@ -123,17 +127,13 @@ fn register_response_to_result(
fn heartbeat_response_to_result(
response: storage::UpdateExecutionManagerHeartbeatResponse,
) -> Result<SessionId, LivenessResponseError> {
match response.result {
Some(update_execution_manager_heartbeat_response::Result::SessionId(session_id)) => {
Ok(session_id)
}
Some(update_execution_manager_heartbeat_response::Result::Error(error)) => {
Err(error.into())
}
None => Err(LivenessResponseError::Transport(
"update execution manager heartbeat response missing result".to_owned(),
)),
let session_id = response.session_id;
if session_id == 0 {
return Err(LivenessResponseError::Transport(
"update execution manager heartbeat response carried a zero session id".to_owned(),
));
}
Ok(session_id)
}

/// Converts a displayable transport-layer error into [`LivenessResponseError::Transport`].
Expand All @@ -158,12 +158,10 @@ mod tests {
const EM_ID: ExecutionManagerId = ExecutionManagerId::from(5);

let response = storage::RegisterExecutionManagerResponse {
result: Some(register_execution_manager_response::Result::Registration(
storage::ExecutionManagerRegistration {
execution_manager_id: EM_ID.get(),
session_id: SESSION_ID,
},
)),
registration: Some(storage::ExecutionManagerRegistration {
execution_manager_id: EM_ID.get(),
session_id: SESSION_ID,
}),
};

let registration = register_response_to_result(response)
Expand All @@ -178,14 +176,37 @@ mod tests {
);
}

#[test]
fn register_response_to_result_rejects_missing_registration() {
let response = storage::RegisterExecutionManagerResponse { registration: None };

assert!(matches!(
register_response_to_result(response),
Err(LivenessResponseError::Transport(_))
));
}

#[test]
fn register_response_to_result_rejects_zero_session_id() {
let response = storage::RegisterExecutionManagerResponse {
registration: Some(storage::ExecutionManagerRegistration {
execution_manager_id: 5,
session_id: 0,
}),
};

assert!(matches!(
register_response_to_result(response),
Err(LivenessResponseError::Transport(_))
));
}

#[test]
fn heartbeat_response_to_result_returns_session_id() {
const SESSION_ID: SessionId = 9;

let response = storage::UpdateExecutionManagerHeartbeatResponse {
result: Some(
update_execution_manager_heartbeat_response::Result::SessionId(SESSION_ID),
),
session_id: SESSION_ID,
};

let session_id = heartbeat_response_to_result(response)
Expand All @@ -195,19 +216,43 @@ mod tests {
}

#[test]
fn liveness_storage_error_maps_invalid_input_to_illegal_id() {
const ERROR_MSG: &str = "bad em id";
fn heartbeat_response_to_result_rejects_zero_session_id() {
let response = storage::UpdateExecutionManagerHeartbeatResponse { session_id: 0 };

let error = storage::ExecutionManagerLivenessError {
err_code: execution_manager_liveness_error::ErrCode::InvalidInput.into(),
message: ERROR_MSG.to_owned(),
};
assert!(matches!(
heartbeat_response_to_result(response),
Err(LivenessResponseError::Transport(_))
));
}

match LivenessResponseError::from(error) {
LivenessResponseError::IllegalId(message) => {
assert_eq!(message, ERROR_MSG);
}
error => panic!("unexpected liveness response error: {error:?}"),
#[test]
fn map_liveness_status_maps_failed_precondition_to_marked_dead() {
let status = tonic::Status::failed_precondition("already dead");

assert!(matches!(
map_liveness_status(&status),
LivenessResponseError::MarkedDead
));
}

#[test]
fn map_liveness_status_maps_invalid_argument_to_illegal_id() {
const ERROR_MSG: &str = "bad em id";
let status = tonic::Status::invalid_argument(ERROR_MSG);

match map_liveness_status(&status) {
LivenessResponseError::IllegalId(message) => assert_eq!(message, ERROR_MSG),
error => panic!("unexpected liveness status mapping: {error:?}"),
}
}

#[test]
fn map_liveness_status_maps_other_codes_to_transport() {
let status = tonic::Status::internal("boom");

assert!(matches!(
map_liveness_status(&status),
LivenessResponseError::Transport(_)
));
}
}
Loading
Loading