diff --git a/Cargo.lock b/Cargo.lock index 9db8a9085a..215232278f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2635,6 +2635,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "gen_ops" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "304de19db7028420975a296ab0fcbbc8e69438c4ed254a1e41e2a7f37d5f0e0a" + [[package]] name = "generic-array" version = "0.14.7" @@ -2812,6 +2818,7 @@ dependencies = [ "prometheus", "prost 0.12.3", "rand 0.8.5", + "range-set-blaze", "serde", "serde_json", "tokio", @@ -3029,9 +3036,9 @@ dependencies = [ [[package]] name = "golem-wit" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce8a760c192419ae1e55fcef20470a248bbd2e19135ae13b6886ddb8619d142" +checksum = "b3263c22aa93cb043f2990a460547669627af71379833003d5605f86d970b301" [[package]] name = "golem-worker-executor" @@ -5650,6 +5657,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "range-set-blaze" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8421b5d459262eabbe49048d362897ff3e3830b44eac6cfe341d6acb2f0f13d2" +dependencies = [ + "gen_ops", + "itertools 0.12.1", + "num-integer", + "num-traits", +] + [[package]] name = "rayon" version = "1.8.1" diff --git a/golem-api-grpc/proto/golem/worker/complete_parameters.proto b/golem-api-grpc/proto/golem/worker/complete_parameters.proto index 539595d05e..6e1571bf1a 100644 --- a/golem-api-grpc/proto/golem/worker/complete_parameters.proto +++ b/golem-api-grpc/proto/golem/worker/complete_parameters.proto @@ -4,6 +4,6 @@ package golem.worker; message CompleteParameters { - int32 oplog_idx = 1; + uint64 oplog_idx = 1; bytes data = 2; } diff --git a/golem-api-grpc/proto/golem/worker/promise_id.proto b/golem-api-grpc/proto/golem/worker/promise_id.proto index 26c0b870b5..da96ea7376 100644 --- a/golem-api-grpc/proto/golem/worker/promise_id.proto +++ b/golem-api-grpc/proto/golem/worker/promise_id.proto @@ -6,5 +6,5 @@ import "golem/worker/worker_id.proto"; message PromiseId { WorkerId worker_id = 1; - int32 oplog_idx = 2; + uint64 oplog_idx = 2; } diff --git a/golem-common/Cargo.toml b/golem-common/Cargo.toml index 98a45e9aba..974d1023e0 100644 --- a/golem-common/Cargo.toml +++ b/golem-common/Cargo.toml @@ -29,6 +29,7 @@ poem-openapi = { workspace = true } prometheus = { workspace = true } prost = { workspace = true } rand = "0.8.5" +range-set-blaze = "0.1.16" serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } diff --git a/golem-common/src/model.rs b/golem-common/src/model/mod.rs similarity index 67% rename from golem-common/src/model.rs rename to golem-common/src/model/mod.rs index 4f34f95f9e..6b42ff1b3f 100644 --- a/golem-common/src/model.rs +++ b/golem-common/src/model/mod.rs @@ -25,20 +25,19 @@ use bincode::enc::write::Writer; use bincode::enc::Encoder; use bincode::error::{DecodeError, EncodeError}; use bincode::{BorrowDecode, Decode, Encode}; -use bytes::Bytes; use derive_more::FromStr; use poem_openapi::registry::{MetaSchema, MetaSchemaRef}; use poem_openapi::types::{ParseFromJSON, ParseFromParameter, ParseResult, ToJSON}; use poem_openapi::{Enum, Object}; -use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize, Serializer}; use serde_json::Value; use uuid::Uuid; +use crate::model::regions::DeletedRegions; use crate::newtype_uuid; -use crate::serialization::{ - deserialize_with_version, serialize, try_deserialize, SERIALIZATION_VERSION_V1, -}; + +pub mod oplog; +pub mod regions; newtype_uuid!( TemplateId, @@ -238,7 +237,7 @@ impl TryFrom for WorkerId { pub struct PromiseId { #[serde(rename = "instance_id")] pub worker_id: WorkerId, - pub oplog_idx: i32, + pub oplog_idx: u64, } impl PromiseId { @@ -509,7 +508,6 @@ pub struct WorkerMetadata { } impl WorkerMetadata { - #[allow(unused)] // TODO pub fn default(worker_id: VersionedWorkerId, account_id: AccountId) -> WorkerMetadata { WorkerMetadata { worker_id, @@ -521,16 +519,22 @@ impl WorkerMetadata { } } +/// Contains status information about a worker according to a given oplog index. +/// This status is just cached information, all fields must be computable by the oplog alone. +/// By having an associated oplog_idx, the cached information can be used together with the +/// tail of the oplog to determine the actual status of the worker. #[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)] pub struct WorkerStatusRecord { pub status: WorkerStatus, - pub oplog_idx: i32, + pub deleted_regions: DeletedRegions, + pub oplog_idx: u64, } impl Default for WorkerStatusRecord { fn default() -> Self { WorkerStatusRecord { status: WorkerStatus::Idle, + deleted_regions: DeletedRegions::new(), oplog_idx: 0, } } @@ -607,29 +611,6 @@ impl From for i32 { } } -impl TryFrom for WorkerMetadata { - type Error = String; - - fn try_from( - value: golem_api_grpc::proto::golem::worker::WorkerMetadata, - ) -> Result { - let worker_id: WorkerId = value.worker_id.ok_or("Missing worker_id")?.try_into()?; - Ok(Self { - worker_id: VersionedWorkerId { - worker_id, - template_version: value.template_version, - }, - args: value.args, - env: value.env.into_iter().collect(), - account_id: value.account_id.ok_or("Missing account_id")?.into(), - last_known_status: WorkerStatusRecord { - status: value.status.try_into()?, - oplog_idx: -1, - }, - }) - } -} - #[derive( Clone, Debug, @@ -732,187 +713,6 @@ impl Display for AccountId { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encode, Decode)] -pub enum OplogEntry { - ImportedFunctionInvoked { - timestamp: Timestamp, - function_name: String, - response: Vec, - wrapped_function_type: WrappedFunctionType, - }, - ExportedFunctionInvoked { - timestamp: Timestamp, - function_name: String, - request: Vec, - invocation_key: Option, - calling_convention: Option, - }, - ExportedFunctionCompleted { - timestamp: Timestamp, - response: Vec, - consumed_fuel: i64, - }, - CreatePromise { - timestamp: Timestamp, - promise_id: PromiseId, - }, - CompletePromise { - timestamp: Timestamp, - promise_id: PromiseId, - data: Vec, - }, - Suspend { - timestamp: Timestamp, - }, - Error { - timestamp: Timestamp, - }, - Debug { - timestamp: Timestamp, - message: String, - }, -} - -impl OplogEntry { - pub fn imported_function_invoked( - timestamp: Timestamp, - function_name: String, - response: &R, - wrapped_function_type: WrappedFunctionType, - ) -> Result { - let serialized_response = serialize(response)?.to_vec(); - - Ok(OplogEntry::ImportedFunctionInvoked { - timestamp, - function_name, - response: serialized_response, - wrapped_function_type, - }) - } - - pub fn exported_function_invoked( - timestamp: Timestamp, - function_name: String, - request: &R, - invocation_key: Option, - calling_convention: Option, - ) -> Result { - let serialized_request = serialize(request)?.to_vec(); - Ok(OplogEntry::ExportedFunctionInvoked { - timestamp, - function_name, - request: serialized_request, - invocation_key, - calling_convention, - }) - } - - pub fn exported_function_completed( - timestamp: Timestamp, - response: &R, - consumed_fuel: i64, - ) -> Result { - let serialized_response = serialize(response)?.to_vec(); - Ok(OplogEntry::ExportedFunctionCompleted { - timestamp, - response: serialized_response, - consumed_fuel, - }) - } - - pub fn response(&self) -> Result, String> { - match &self { - OplogEntry::ImportedFunctionInvoked { response, .. } => { - let response_bytes: Bytes = Bytes::copy_from_slice(response); - - // In the v1 serialization format we did not have version prefix in the payloads. - // We can assume though that if the payload starts with 2, it is serialized with the - // v2 format because neither JSON nor protobuf (the two payload formats used in v1 for payloads) - // start with 2 (This was verified with a simple test ValProtobufPrefixByteValidation). - // So if the first byte is not 1 or 2 we assume it is a v1 format and deserialize it as JSON. - match try_deserialize(&response_bytes)? { - Some(result) => Ok(Some(result)), - None => Ok(Some(deserialize_with_version( - &response_bytes, - SERIALIZATION_VERSION_V1, - )?)), - } - } - OplogEntry::ExportedFunctionCompleted { response, .. } => { - let response_bytes: Bytes = Bytes::copy_from_slice(response); - - // See the comment above for the explanation of this logic - match try_deserialize(&response_bytes)? { - Some(result) => Ok(Some(result)), - None => Ok(Some(deserialize_with_version( - &response_bytes, - SERIALIZATION_VERSION_V1, - )?)), - } - } - _ => Ok(None), - } - } - - pub fn payload_as_val_array( - &self, - ) -> Result>, String> { - // This is a special case of a possible generic request() accessor, because in v1 the only - // data type we serialized was Vec and it was done in a special way (every element serialized - // via protobuf separately, then an array of byte arrays serialized into JSON) - match &self { - OplogEntry::ExportedFunctionInvoked { - function_name, - request, - .. - } => { - let request_bytes: Bytes = Bytes::copy_from_slice(request); - self.try_decode_val_array_payload(function_name, &request_bytes) - } - OplogEntry::ExportedFunctionCompleted { response, .. } => { - let response_bytes: Bytes = Bytes::copy_from_slice(response); - self.try_decode_val_array_payload("?", &response_bytes) - } - _ => Ok(None), - } - } - - fn try_decode_val_array_payload( - &self, - function_name: &str, - payload: &Bytes, - ) -> Result>, String> { - match try_deserialize(payload)? { - Some(result) => Ok(Some(result)), - None => { - let deserialized_array: Vec> = serde_json::from_slice(payload) - .unwrap_or_else(|err| { - panic!( - "Failed to deserialize oplog payload: {:?}: {err}", - std::str::from_utf8(payload).unwrap_or("???") - ) - }); - let function_input = deserialized_array - .iter() - .map(|serialized_value| { - ::decode(serialized_value.as_slice()) - .unwrap_or_else(|err| panic!("Failed to deserialize function input {:?} for {function_name}: {err}", serialized_value)) - }) - .collect::>(); - Ok(Some(function_input)) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)] -pub enum WrappedFunctionType { - ReadLocal, - WriteLocal, - ReadRemote, - WriteRemote, -} - #[derive(Debug, PartialEq, Eq, Clone)] pub struct ParsedFunctionName { pub interface: Option, @@ -976,13 +776,11 @@ pub fn parse_function_name(name: &str) -> ParsedFunctionName { #[cfg(test)] mod tests { - use crate::model::{parse_function_name, AccountId}; - use crate::model::{CallingConvention, InvocationKey, Timestamp}; - use crate::model::{OplogEntry, WrappedFunctionType}; use bincode::{Decode, Encode}; - use golem_wasm_rpc::protobuf::{val, Val, ValResult}; use serde::{Deserialize, Serialize}; + use crate::model::{parse_function_name, AccountId}; + #[test] fn parse_function_name_global() { let parsed = parse_function_name("run-example"); @@ -1104,169 +902,4 @@ mod tests { let json = serde_json::to_string(&example).unwrap(); assert_eq!(json, "{\"account_id\":\"account-1\"}"); } - - #[test] - fn oplog_entry_imported_function_invoked_payload_roundtrip() { - let timestamp = Timestamp::now_utc(); - let entry = OplogEntry::imported_function_invoked( - timestamp, - "function_name".to_string(), - &("example payload".to_string()), - WrappedFunctionType::ReadLocal, - ) - .unwrap(); - - if let OplogEntry::ImportedFunctionInvoked { response, .. } = &entry { - assert_eq!(response.len(), 17); - } else { - unreachable!() - } - - let response = entry.response::().unwrap().unwrap(); - - assert_eq!(response, "example payload"); - } - - #[test] - fn oplog_entry_imported_function_invoked_payload_v1() { - let timestamp = Timestamp::now_utc(); - let entry = OplogEntry::ImportedFunctionInvoked { - timestamp, - function_name: "function_name".to_string(), - response: serde_json::to_vec("example payload").unwrap(), - wrapped_function_type: WrappedFunctionType::ReadLocal, - }; - - let response = entry.response::().unwrap().unwrap(); - - assert_eq!(response, "example payload"); - } - - #[test] - fn oplog_entry_exported_function_invoked_payload_roundtrip() { - let timestamp = Timestamp::now_utc(); - - let val1 = Val { - val: Some(val::Val::Result(Box::new(ValResult { - discriminant: 0, - value: Some(Box::new(Val { - val: Some(val::Val::U64(10)), - })), - }))), - }; - let entry = OplogEntry::exported_function_invoked( - timestamp, - "function_name".to_string(), - &vec![val1.clone()], - Some(InvocationKey { - value: "invocation_key".to_string(), - }), - Some(CallingConvention::Stdio), - ) - .unwrap(); - - if let OplogEntry::ExportedFunctionInvoked { request, .. } = &entry { - assert_eq!(request.len(), 9); - } else { - unreachable!() - } - - let request = entry.payload_as_val_array().unwrap().unwrap(); - - assert_eq!(request, vec![val1]); - } - - #[test] - fn oplog_entry_exported_function_invoked_payload_v1() { - let timestamp = Timestamp::now_utc(); - - let val1 = Val { - val: Some(val::Val::Result(Box::new(ValResult { - discriminant: 0, - value: Some(Box::new(Val { - val: Some(val::Val::U64(10)), - })), - }))), - }; - let val1_bytes = prost::Message::encode_to_vec(&val1); - let request_bytes = serde_json::to_vec(&vec![val1_bytes]).unwrap(); - - let entry = OplogEntry::ExportedFunctionInvoked { - timestamp, - function_name: "function_name".to_string(), - request: request_bytes, - invocation_key: Some(InvocationKey { - value: "invocation_key".to_string(), - }), - calling_convention: Some(CallingConvention::Stdio), - }; - - let request = entry.payload_as_val_array().unwrap().unwrap(); - assert_eq!(request, vec![val1]); - } - - #[test] - fn oplog_entry_exported_function_completed_roundtrip() { - let timestamp = Timestamp::now_utc(); - - let val1 = Val { - val: Some(val::Val::Result(Box::new(ValResult { - discriminant: 0, - value: Some(Box::new(Val { - val: Some(val::Val::U64(10)), - })), - }))), - }; - let val2 = Val { - val: Some(val::Val::String("something".to_string())), - }; - - let entry = OplogEntry::exported_function_completed( - timestamp, - &vec![val1.clone(), val2.clone()], - 1_000_000_000, - ) - .unwrap(); - - if let OplogEntry::ExportedFunctionCompleted { response, .. } = &entry { - assert_eq!(response.len(), 21); - } else { - unreachable!() - } - - let response = entry.payload_as_val_array().unwrap().unwrap(); - - assert_eq!(response, vec![val1, val2]); - } - - #[test] - fn oplog_entry_exported_function_completed_v1() { - let timestamp = Timestamp::now_utc(); - - let val1 = Val { - val: Some(val::Val::Result(Box::new(ValResult { - discriminant: 0, - value: Some(Box::new(Val { - val: Some(val::Val::U64(10)), - })), - }))), - }; - let val1_bytes = prost::Message::encode_to_vec(&val1); - let val2 = Val { - val: Some(val::Val::String("something".to_string())), - }; - let val2_bytes = prost::Message::encode_to_vec(&val2); - - let response_bytes = serde_json::to_vec(&vec![val1_bytes, val2_bytes]).unwrap(); - - let entry = OplogEntry::ExportedFunctionCompleted { - timestamp, - response: response_bytes, - consumed_fuel: 1_000_000_000, - }; - - let response = entry.payload_as_val_array().unwrap().unwrap(); - - assert_eq!(response, vec![val1, val2]); - } } diff --git a/golem-common/src/model/oplog.rs b/golem-common/src/model/oplog.rs new file mode 100644 index 0000000000..bd41115483 --- /dev/null +++ b/golem-common/src/model/oplog.rs @@ -0,0 +1,385 @@ +use crate::model::regions::OplogRegion; +use crate::model::{CallingConvention, InvocationKey, PromiseId, Timestamp}; +use crate::serialization::{ + deserialize_with_version, serialize, try_deserialize, SERIALIZATION_VERSION_V1, +}; +use bincode::{Decode, Encode}; +use bytes::Bytes; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encode, Decode)] +pub enum OplogEntry { + /// The worker invoked a host function + ImportedFunctionInvoked { + timestamp: Timestamp, + function_name: String, + response: Vec, + wrapped_function_type: WrappedFunctionType, + }, + /// The worker has been invoked + ExportedFunctionInvoked { + timestamp: Timestamp, + function_name: String, + request: Vec, + invocation_key: Option, + calling_convention: Option, + }, + /// The worker has completed an invocation + ExportedFunctionCompleted { + timestamp: Timestamp, + response: Vec, + consumed_fuel: i64, + }, + /// Promise created + CreatePromise { + timestamp: Timestamp, + promise_id: PromiseId, + }, + /// Promise completed + CompletePromise { + timestamp: Timestamp, + promise_id: PromiseId, + data: Vec, + }, + /// Worker suspended + Suspend { timestamp: Timestamp }, + /// Worker failed + Error { timestamp: Timestamp }, + /// Debug log entry + Debug { + timestamp: Timestamp, + message: String, + }, + /// Marker entry added when get-oplog-index is called from the worker, to make the jumping behavior + /// more predictable. + NoOp { timestamp: Timestamp }, + /// The worker needs to recover up to the given target oplog index and continue running from + /// the source oplog index from there + /// `jump` is an oplog region representing that from the end of that region we want to go back to the start and + /// ignore all recorded operations in between. + Jump { + timestamp: Timestamp, + jump: OplogRegion, + }, +} + +impl OplogEntry { + pub fn imported_function_invoked( + timestamp: Timestamp, + function_name: String, + response: &R, + wrapped_function_type: WrappedFunctionType, + ) -> Result { + let serialized_response = serialize(response)?.to_vec(); + + Ok(OplogEntry::ImportedFunctionInvoked { + timestamp, + function_name, + response: serialized_response, + wrapped_function_type, + }) + } + + pub fn exported_function_invoked( + timestamp: Timestamp, + function_name: String, + request: &R, + invocation_key: Option, + calling_convention: Option, + ) -> Result { + let serialized_request = serialize(request)?.to_vec(); + Ok(OplogEntry::ExportedFunctionInvoked { + timestamp, + function_name, + request: serialized_request, + invocation_key, + calling_convention, + }) + } + + pub fn exported_function_completed( + timestamp: Timestamp, + response: &R, + consumed_fuel: i64, + ) -> Result { + let serialized_response = serialize(response)?.to_vec(); + Ok(OplogEntry::ExportedFunctionCompleted { + timestamp, + response: serialized_response, + consumed_fuel, + }) + } + + pub fn response(&self) -> Result, String> { + match &self { + OplogEntry::ImportedFunctionInvoked { response, .. } => { + let response_bytes: Bytes = Bytes::copy_from_slice(response); + + // In the v1 serialization format we did not have version prefix in the payloads. + // We can assume though that if the payload starts with 2, it is serialized with the + // v2 format because neither JSON nor protobuf (the two payload formats used in v1 for payloads) + // start with 2 (This was verified with a simple test ValProtobufPrefixByteValidation). + // So if the first byte is not 1 or 2 we assume it is a v1 format and deserialize it as JSON. + match try_deserialize(&response_bytes)? { + Some(result) => Ok(Some(result)), + None => Ok(Some(deserialize_with_version( + &response_bytes, + SERIALIZATION_VERSION_V1, + )?)), + } + } + OplogEntry::ExportedFunctionCompleted { response, .. } => { + let response_bytes: Bytes = Bytes::copy_from_slice(response); + + // See the comment above for the explanation of this logic + match try_deserialize(&response_bytes)? { + Some(result) => Ok(Some(result)), + None => Ok(Some(deserialize_with_version( + &response_bytes, + SERIALIZATION_VERSION_V1, + )?)), + } + } + _ => Ok(None), + } + } + + pub fn payload_as_val_array( + &self, + ) -> Result>, String> { + // This is a special case of a possible generic request() accessor, because in v1 the only + // data type we serialized was Vec and it was done in a special way (every element serialized + // via protobuf separately, then an array of byte arrays serialized into JSON) + match &self { + OplogEntry::ExportedFunctionInvoked { + function_name, + request, + .. + } => { + let request_bytes: Bytes = Bytes::copy_from_slice(request); + self.try_decode_val_array_payload(function_name, &request_bytes) + } + OplogEntry::ExportedFunctionCompleted { response, .. } => { + let response_bytes: Bytes = Bytes::copy_from_slice(response); + self.try_decode_val_array_payload("?", &response_bytes) + } + _ => Ok(None), + } + } + + fn try_decode_val_array_payload( + &self, + function_name: &str, + payload: &Bytes, + ) -> Result>, String> { + match try_deserialize(payload)? { + Some(result) => Ok(Some(result)), + None => { + let deserialized_array: Vec> = serde_json::from_slice(payload) + .unwrap_or_else(|err| { + panic!( + "Failed to deserialize oplog payload: {:?}: {err}", + std::str::from_utf8(payload).unwrap_or("???") + ) + }); + let function_input = deserialized_array + .iter() + .map(|serialized_value| { + ::decode(serialized_value.as_slice()) + .unwrap_or_else(|err| panic!("Failed to deserialize function input {:?} for {function_name}: {err}", serialized_value)) + }) + .collect::>(); + Ok(Some(function_input)) + } + } + } + + pub fn jump(timestamp: Timestamp, jump: OplogRegion) -> OplogEntry { + OplogEntry::Jump { timestamp, jump } + } + + pub fn nop(timestamp: Timestamp) -> OplogEntry { + OplogEntry::NoOp { timestamp } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)] +pub enum WrappedFunctionType { + ReadLocal, + WriteLocal, + ReadRemote, + WriteRemote, +} + +#[cfg(test)] +mod tests { + use super::{OplogEntry, WrappedFunctionType}; + use crate::model::{CallingConvention, InvocationKey, Timestamp}; + use golem_wasm_rpc::protobuf::{val, Val, ValResult}; + + #[test] + fn oplog_entry_imported_function_invoked_payload_roundtrip() { + let timestamp = Timestamp::now_utc(); + let entry = OplogEntry::imported_function_invoked( + timestamp, + "function_name".to_string(), + &("example payload".to_string()), + WrappedFunctionType::ReadLocal, + ) + .unwrap(); + + if let OplogEntry::ImportedFunctionInvoked { response, .. } = &entry { + assert_eq!(response.len(), 17); + } else { + unreachable!() + } + + let response = entry.response::().unwrap().unwrap(); + + assert_eq!(response, "example payload"); + } + + #[test] + fn oplog_entry_imported_function_invoked_payload_v1() { + let timestamp = Timestamp::now_utc(); + let entry = OplogEntry::ImportedFunctionInvoked { + timestamp, + function_name: "function_name".to_string(), + response: serde_json::to_vec("example payload").unwrap(), + wrapped_function_type: WrappedFunctionType::ReadLocal, + }; + + let response = entry.response::().unwrap().unwrap(); + + assert_eq!(response, "example payload"); + } + + #[test] + fn oplog_entry_exported_function_invoked_payload_roundtrip() { + let timestamp = Timestamp::now_utc(); + + let val1 = Val { + val: Some(val::Val::Result(Box::new(ValResult { + discriminant: 0, + value: Some(Box::new(Val { + val: Some(val::Val::U64(10)), + })), + }))), + }; + let entry = OplogEntry::exported_function_invoked( + timestamp, + "function_name".to_string(), + &vec![val1.clone()], + Some(InvocationKey { + value: "invocation_key".to_string(), + }), + Some(CallingConvention::Stdio), + ) + .unwrap(); + + if let OplogEntry::ExportedFunctionInvoked { request, .. } = &entry { + assert_eq!(request.len(), 9); + } else { + unreachable!() + } + + let request = entry.payload_as_val_array().unwrap().unwrap(); + + assert_eq!(request, vec![val1]); + } + + #[test] + fn oplog_entry_exported_function_invoked_payload_v1() { + let timestamp = Timestamp::now_utc(); + + let val1 = Val { + val: Some(val::Val::Result(Box::new(ValResult { + discriminant: 0, + value: Some(Box::new(Val { + val: Some(val::Val::U64(10)), + })), + }))), + }; + let val1_bytes = prost::Message::encode_to_vec(&val1); + let request_bytes = serde_json::to_vec(&vec![val1_bytes]).unwrap(); + + let entry = OplogEntry::ExportedFunctionInvoked { + timestamp, + function_name: "function_name".to_string(), + request: request_bytes, + invocation_key: Some(InvocationKey { + value: "invocation_key".to_string(), + }), + calling_convention: Some(CallingConvention::Stdio), + }; + + let request = entry.payload_as_val_array().unwrap().unwrap(); + assert_eq!(request, vec![val1]); + } + + #[test] + fn oplog_entry_exported_function_completed_roundtrip() { + let timestamp = Timestamp::now_utc(); + + let val1 = Val { + val: Some(val::Val::Result(Box::new(ValResult { + discriminant: 0, + value: Some(Box::new(Val { + val: Some(val::Val::U64(10)), + })), + }))), + }; + let val2 = Val { + val: Some(val::Val::String("something".to_string())), + }; + + let entry = OplogEntry::exported_function_completed( + timestamp, + &vec![val1.clone(), val2.clone()], + 1_000_000_000, + ) + .unwrap(); + + if let OplogEntry::ExportedFunctionCompleted { response, .. } = &entry { + assert_eq!(response.len(), 21); + } else { + unreachable!() + } + + let response = entry.payload_as_val_array().unwrap().unwrap(); + + assert_eq!(response, vec![val1, val2]); + } + + #[test] + fn oplog_entry_exported_function_completed_v1() { + let timestamp = Timestamp::now_utc(); + + let val1 = Val { + val: Some(val::Val::Result(Box::new(ValResult { + discriminant: 0, + value: Some(Box::new(Val { + val: Some(val::Val::U64(10)), + })), + }))), + }; + let val1_bytes = prost::Message::encode_to_vec(&val1); + let val2 = Val { + val: Some(val::Val::String("something".to_string())), + }; + let val2_bytes = prost::Message::encode_to_vec(&val2); + + let response_bytes = serde_json::to_vec(&vec![val1_bytes, val2_bytes]).unwrap(); + + let entry = OplogEntry::ExportedFunctionCompleted { + timestamp, + response: response_bytes, + consumed_fuel: 1_000_000_000, + }; + + let response = entry.payload_as_val_array().unwrap().unwrap(); + + assert_eq!(response, vec![val1, val2]); + } +} diff --git a/golem-common/src/model/regions.rs b/golem-common/src/model/regions.rs new file mode 100644 index 0000000000..8a053b8148 --- /dev/null +++ b/golem-common/src/model/regions.rs @@ -0,0 +1,240 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::btree_map::{IntoValues, Values}; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; +use std::ops::Bound::{Included, Unbounded}; +use std::ops::RangeInclusive; + +use bincode::{Decode, Encode}; +use range_set_blaze::RangeSetBlaze; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encode, Decode)] +pub struct OplogRegion { + pub start: u64, + pub end: u64, +} + +impl OplogRegion { + pub fn contains(&self, target: u64) -> bool { + target > self.end && target <= self.start + } + + pub fn union(&self, other: &OplogRegion) -> Option { + if self.contains(other.start) || other.contains(self.start) { + Some(OplogRegion { + start: self.start.min(other.start), + end: self.end.max(other.end), + }) + } else { + None + } + } + + pub fn from_range(range: RangeInclusive) -> OplogRegion { + OplogRegion { + start: *range.start(), + end: *range.end(), + } + } + + pub fn to_range(&self) -> RangeInclusive { + self.start..=self.end + } +} + +impl Display for OplogRegion { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "<{} => {}>", self.start, self.end) + } +} + +/// Temporary builder for building up a `DeletedRegions` structure using an efficient data +/// structure for merging ranges as they are added. +pub struct DeletedRegionsBuilder { + regions: RangeSetBlaze, +} + +impl Default for DeletedRegionsBuilder { + fn default() -> Self { + Self::new() + } +} + +impl DeletedRegionsBuilder { + pub fn new() -> Self { + Self { + regions: RangeSetBlaze::new(), + } + } + + pub fn from_regions(regions: impl IntoIterator) -> Self { + Self { + regions: RangeSetBlaze::from_iter(regions.into_iter().map(|region| region.to_range())), + } + } + + /// Adds a new region to the list of deleted regions + pub fn add(&mut self, region: OplogRegion) { + self.regions.ranges_insert(region.to_range()); + } + + pub fn build(self) -> DeletedRegions { + DeletedRegions::from_regions(self.regions.into_ranges().map(OplogRegion::from_range)) + } +} + +/// Structure holding all the regions deleted from the oplog by jumps +#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)] +pub struct DeletedRegions { + regions: BTreeMap, +} + +impl Default for DeletedRegions { + fn default() -> Self { + Self::new() + } +} + +impl DeletedRegions { + /// Constructs an empty set of deleted regions + pub fn new() -> Self { + Self { + regions: BTreeMap::new(), + } + } + + /// Initializes from known list of deleted oplog regions + pub fn from_regions(regions: impl IntoIterator) -> Self { + Self { + regions: BTreeMap::from_iter(regions.into_iter().map(|region| (region.start, region))), + } + } + + /// Adds a new region to the list of deleted regions + pub fn add(&mut self, region: OplogRegion) { + // We rebuild the map to make sure overlapping regions are properly merged + let mut builder = DeletedRegionsBuilder::from_regions(self.regions.clone().into_values()); + builder.add(region); + self.regions = builder.build().regions; + } + + /// Returns the list of deleted regions + pub fn regions(&self) -> Values<'_, u64, OplogRegion> { + self.regions.values() + } + + /// Becomes the list of deleted regions + pub fn into_regions(self) -> IntoValues { + self.regions.into_values() + } + + /// Gets the next deleted region after, possibly including, the given oplog index. + /// Returns None if there are no more deleted regions. + pub fn find_next_deleted_region(&self, from: u64) -> Option { + self.regions + .range((Included(from), Unbounded)) + .next() + .map(|(_, region)| region.clone()) + } + + pub fn is_in_deleted_region(&self, oplog_index: u64) -> bool { + self.regions + .values() + .any(|region| region.contains(oplog_index)) + } +} + +impl Display for DeletedRegions { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{}]", + self.regions + .values() + .map(|region| region.to_string()) + .collect::>() + .join(", ") + ) + } +} + +#[cfg(test)] +mod tests { + use crate::model::regions::{DeletedRegionsBuilder, OplogRegion}; + + #[test] + pub fn builder_from_overlapping_ranges() { + let mut builder = DeletedRegionsBuilder::new(); + builder.add(OplogRegion { start: 2, end: 8 }); + builder.add(OplogRegion { start: 2, end: 14 }); + builder.add(OplogRegion { start: 20, end: 22 }); + let deleted_regions = builder.build(); + + assert_eq!( + deleted_regions.into_regions().collect::>(), + vec![ + OplogRegion { start: 2, end: 14 }, + OplogRegion { start: 20, end: 22 }, + ] + ); + } + + #[test] + pub fn builder_from_initial_state() { + let mut builder = DeletedRegionsBuilder::from_regions(vec![ + OplogRegion { start: 2, end: 8 }, + OplogRegion { start: 20, end: 22 }, + ]); + + builder.add(OplogRegion { start: 20, end: 24 }); + builder.add(OplogRegion { start: 30, end: 40 }); + + let deleted_regions = builder.build(); + + assert_eq!( + deleted_regions.into_regions().collect::>(), + vec![ + OplogRegion { start: 2, end: 8 }, + OplogRegion { start: 20, end: 24 }, + OplogRegion { start: 30, end: 40 }, + ] + ); + } + + #[test] + pub fn find_next_deleted_region() { + let deleted_regions = DeletedRegionsBuilder::from_regions(vec![ + OplogRegion { start: 2, end: 8 }, + OplogRegion { start: 20, end: 22 }, + ]) + .build(); + + assert_eq!( + deleted_regions.find_next_deleted_region(0), + Some(OplogRegion { start: 2, end: 8 }) + ); + assert_eq!( + deleted_regions.find_next_deleted_region(2), + Some(OplogRegion { start: 2, end: 8 }) + ); + assert_eq!( + deleted_regions.find_next_deleted_region(8), + Some(OplogRegion { start: 20, end: 22 }) + ); + assert_eq!(deleted_regions.find_next_deleted_region(22), None); + } +} diff --git a/golem-common/src/retries.rs b/golem-common/src/retries.rs index f49ceaca27..cf76df9d51 100644 --- a/golem-common/src/retries.rs +++ b/golem-common/src/retries.rs @@ -26,14 +26,14 @@ use crate::metrics::external_calls::{ /// Returns the delay to be waited before the next retry attempt. /// To be called after a failed attempt, with the number of attempts so far. /// Returns None if the maximum number of attempts has been reached. -pub fn get_delay(config: &RetryConfig, attempts: u32) -> Option { +pub fn get_delay(config: &RetryConfig, attempts: u64) -> Option { // Exponential backoff algorithm inspired by fred::pool::ReconnectPolicy::Exponential - if attempts >= config.max_attempts { + if attempts >= (config.max_attempts as u64) { return None; } let base_delay = (config.multiplier as u64) - .saturating_pow(attempts - 1) + .saturating_pow(attempts.saturating_sub(1).try_into().unwrap_or(0)) .saturating_mul(config.min_delay.as_millis() as u64); let delay = Duration::from_millis(std::cmp::min( @@ -130,7 +130,7 @@ mod tests { ) } - fn capture_delays(config: &RetryConfig, attempts: &mut u32, delays: &mut Vec) { + fn capture_delays(config: &RetryConfig, attempts: &mut u64, delays: &mut Vec) { loop { *attempts += 1; let delay = super::get_delay(config, *attempts); diff --git a/golem-service-base/src/model.rs b/golem-service-base/src/model.rs index b03277cd2c..45d30089d2 100644 --- a/golem-service-base/src/model.rs +++ b/golem-service-base/src/model.rs @@ -1964,7 +1964,7 @@ impl From for golem_api_grpc::proto::golem::worker::Versioned #[serde(rename_all = "camelCase")] #[oai(rename_all = "camelCase")] pub struct CompleteParameters { - pub oplog_idx: i32, + pub oplog_idx: u64, pub data: Vec, } @@ -1984,7 +1984,7 @@ impl From for golem_api_grpc::proto::golem::worker::Complete #[oai(rename_all = "camelCase")] pub struct PromiseId { pub worker_id: WorkerId, - pub oplog_idx: i32, + pub oplog_idx: u64, } impl TryFrom for PromiseId { diff --git a/golem-worker-executor-base/Cargo.toml b/golem-worker-executor-base/Cargo.toml index 0d33ff0cfc..5070bff107 100644 --- a/golem-worker-executor-base/Cargo.toml +++ b/golem-worker-executor-base/Cargo.toml @@ -42,7 +42,7 @@ fs-set-times = "0.20.1" futures = { workspace = true } futures-util = { workspace = true } gethostname = "0.4.3" -golem-wit = "0.1.0" +golem-wit = "0.2.0" http = { workspace = true } http_02 = { package = "http", version = "0.2.11" } http-body = "1.0.0" # keep in sync with wasmtime diff --git a/golem-worker-executor-base/src/durable_host/blobstore/container.rs b/golem-worker-executor-base/src/durable_host/blobstore/container.rs index 56bfd70c43..26766bce8e 100644 --- a/golem-worker-executor-base/src/durable_host/blobstore/container.rs +++ b/golem-worker-executor-base/src/durable_host/blobstore/container.rs @@ -13,7 +13,7 @@ // limitations under the License. use async_trait::async_trait; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime::component::Resource; use wasmtime_wasi::preview2::WasiView; diff --git a/golem-worker-executor-base/src/durable_host/blobstore/mod.rs b/golem-worker-executor-base/src/durable_host/blobstore/mod.rs index db90999130..96bf23556b 100644 --- a/golem-worker-executor-base/src/durable_host/blobstore/mod.rs +++ b/golem-worker-executor-base/src/durable_host/blobstore/mod.rs @@ -16,7 +16,7 @@ pub mod container; pub mod types; use async_trait::async_trait; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime::component::Resource; use wasmtime_wasi::preview2::WasiView; diff --git a/golem-worker-executor-base/src/durable_host/cli/environment.rs b/golem-worker-executor-base/src/durable_host/cli/environment.rs index d784285f4c..34912ab642 100644 --- a/golem-worker-executor-base/src/durable_host/cli/environment.rs +++ b/golem-worker-executor-base/src/durable_host/cli/environment.rs @@ -18,7 +18,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::cli::environment::Host; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/clocks/monotonic_clock.rs b/golem-worker-executor-base/src/durable_host/clocks/monotonic_clock.rs index 1782d77dc4..58a708b21f 100644 --- a/golem-worker-executor-base/src/durable_host/clocks/monotonic_clock.rs +++ b/golem-worker-executor-base/src/durable_host/clocks/monotonic_clock.rs @@ -19,7 +19,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::clocks::monotonic_clock::{ Duration, Host, Instant, Pollable, }; diff --git a/golem-worker-executor-base/src/durable_host/clocks/wall_clock.rs b/golem-worker-executor-base/src/durable_host/clocks/wall_clock.rs index 6ab8bceaf0..44b99e88f7 100644 --- a/golem-worker-executor-base/src/durable_host/clocks/wall_clock.rs +++ b/golem-worker-executor-base/src/durable_host/clocks/wall_clock.rs @@ -18,7 +18,7 @@ use crate::durable_host::serialized::{SerializableDateTime, SerializableError}; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::clocks::wall_clock::{Datetime, Host}; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/filesystem/preopens.rs b/golem-worker-executor-base/src/durable_host/filesystem/preopens.rs index 64611d8f82..6d9d4eb073 100644 --- a/golem-worker-executor-base/src/durable_host/filesystem/preopens.rs +++ b/golem-worker-executor-base/src/durable_host/filesystem/preopens.rs @@ -20,7 +20,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::filesystem::preopens::{Descriptor, Host}; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/filesystem/types.rs b/golem-worker-executor-base/src/durable_host/filesystem/types.rs index bcd9aa9529..ff8b4072c2 100644 --- a/golem-worker-executor-base/src/durable_host/filesystem/types.rs +++ b/golem-worker-executor-base/src/durable_host/filesystem/types.rs @@ -28,7 +28,7 @@ use crate::durable_host::serialized::{ use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::clocks::wall_clock::Datetime; use wasmtime_wasi::preview2::bindings::filesystem::types::{ Advice, Descriptor, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, diff --git a/golem-worker-executor-base/src/durable_host/golem/mod.rs b/golem-worker-executor-base/src/durable_host/golem/mod.rs index 9381d37837..9031889523 100644 --- a/golem-worker-executor-base/src/durable_host/golem/mod.rs +++ b/golem-worker-executor-base/src/durable_host/golem/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use anyhow::anyhow; use async_trait::async_trait; use tracing::debug; use uuid::Uuid; @@ -21,8 +22,11 @@ use crate::durable_host::DurableWorkerCtx; use crate::metrics::wasm::record_host_function_call; use crate::model::InterruptKind; use crate::preview2::golem; +use crate::preview2::golem::api::host::OplogIndex; use crate::workerctx::WorkerCtx; -use golem_common::model::{PromiseId, TemplateId, WorkerId}; +use golem_common::model::oplog::OplogEntry; +use golem_common::model::regions::OplogRegion; +use golem_common::model::{PromiseId, TemplateId, Timestamp, WorkerId}; #[async_trait] impl golem::api::host::Host for DurableWorkerCtx { @@ -82,6 +86,58 @@ impl golem::api::host::Host for DurableWorkerCtx { ); Ok(golem::rpc::types::Uri { value: uri.value }) } + + async fn get_oplog_index(&mut self) -> anyhow::Result { + record_host_function_call("golem::api", "get_oplog_index"); + let result = self.private_state.oplog_idx; + if self.is_live() { + self.set_oplog_entry(OplogEntry::nop(Timestamp::now_utc())) + .await; + } else { + let _ = self.get_oplog_entry_marker().await?; + } + Ok(result) + } + + async fn set_oplog_index(&mut self, oplog_idx: OplogIndex) -> anyhow::Result<()> { + record_host_function_call("golem::api", "set_oplog_index"); + let jump_source = self.private_state.oplog_idx; + let jump_target = oplog_idx; + if jump_target > jump_source { + Err(anyhow!( + "Attempted to jump forward in oplog to index {jump_target} from {jump_source}" + )) + } else if self + .private_state + .deleted_regions + .is_in_deleted_region(jump_target) + { + Err(anyhow!( + "Attempted to jump to a deleted region in oplog to index {jump_target} from {jump_source}" + )) + } else if self.is_live() { + let jump = OplogRegion { + start: jump_target, + end: jump_source, + }; + + // Write an oplog entry with the new jump and then restart the worker + self.private_state.deleted_regions.add(jump.clone()); + self.set_oplog_entry(OplogEntry::jump(Timestamp::now_utc(), jump)) + .await; + self.commit_oplog().await; + + debug!( + "Interrupting live execution of {} for jumping from {jump_source} to {jump_target}", + self.worker_id + ); + Err(InterruptKind::Jump.into()) + } else { + // In replay mode we never have to do anything here + debug!("Ignoring replayed set_oplog_index for {}", self.worker_id); + Ok(()) + } + } } impl From for golem::api::host::WorkerId { diff --git a/golem-worker-executor-base/src/durable_host/http/types.rs b/golem-worker-executor-base/src/durable_host/http/types.rs index e70faf66c5..8e0ad1c895 100644 --- a/golem-worker-executor-base/src/durable_host/http/types.rs +++ b/golem-worker-executor-base/src/durable_host/http/types.rs @@ -20,7 +20,6 @@ use http::{HeaderName, HeaderValue}; use std::collections::HashMap; use std::str::FromStr; -use tracing::info; use wasmtime::component::Resource; use wasmtime_wasi::preview2::subscribe; @@ -33,7 +32,7 @@ use crate::durable_host::http::serialized::{ }; use crate::durable_host::serialized::SerializableError; use crate::workerctx::WorkerCtx; -use golem_common::model::{OplogEntry, WrappedFunctionType}; +use golem_common::model::oplog::{OplogEntry, WrappedFunctionType}; use wasmtime_wasi_http::bindings::wasi::http::types::{ Duration, ErrorCode, FieldKey, FieldValue, Fields, FutureIncomingResponse, FutureTrailers, HeaderError, Headers, Host, HostFields, HostFutureIncomingResponse, HostFutureTrailers, @@ -267,7 +266,6 @@ impl HostRequestOptions for DurableWorkerCtx { ms: Option, ) -> anyhow::Result> { record_host_function_call("http::types::request_options", "set_connect_timeout_ms"); - info!("set_connect_timeout {ms:?}"); HostRequestOptions::set_connect_timeout(&mut self.as_wasi_http_view(), self_, ms) } diff --git a/golem-worker-executor-base/src/durable_host/io/streams.rs b/golem-worker-executor-base/src/durable_host/io/streams.rs index d056cf5a3c..613b8ca0bb 100644 --- a/golem-worker-executor-base/src/durable_host/io/streams.rs +++ b/golem-worker-executor-base/src/durable_host/io/streams.rs @@ -21,7 +21,7 @@ use crate::durable_host::serialized::SerializableStreamError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::io::streams::{ Host, HostInputStream, HostOutputStream, InputStream, OutputStream, Pollable, }; diff --git a/golem-worker-executor-base/src/durable_host/keyvalue/eventual.rs b/golem-worker-executor-base/src/durable_host/keyvalue/eventual.rs index 4e573559a9..bcaedb2f15 100644 --- a/golem-worker-executor-base/src/durable_host/keyvalue/eventual.rs +++ b/golem-worker-executor-base/src/durable_host/keyvalue/eventual.rs @@ -13,7 +13,7 @@ // limitations under the License. use async_trait::async_trait; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime::component::Resource; use wasmtime_wasi::preview2::WasiView; diff --git a/golem-worker-executor-base/src/durable_host/keyvalue/eventual_batch.rs b/golem-worker-executor-base/src/durable_host/keyvalue/eventual_batch.rs index ecb797a56c..c8afdd5834 100644 --- a/golem-worker-executor-base/src/durable_host/keyvalue/eventual_batch.rs +++ b/golem-worker-executor-base/src/durable_host/keyvalue/eventual_batch.rs @@ -13,7 +13,7 @@ // limitations under the License. use async_trait::async_trait; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime::component::Resource; use wasmtime_wasi::preview2::{ResourceTableError, WasiView}; diff --git a/golem-worker-executor-base/src/durable_host/mod.rs b/golem-worker-executor-base/src/durable_host/mod.rs index 93d96f5d42..4ebc5b1051 100644 --- a/golem-worker-executor-base/src/durable_host/mod.rs +++ b/golem-worker-executor-base/src/durable_host/mod.rs @@ -24,7 +24,7 @@ use std::string::FromUtf8Error; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; -use crate::error::{is_interrupt, is_suspend, GolemError}; +use crate::error::{is_interrupt, is_jump, is_suspend, GolemError}; use crate::invocation::invoke_worker; use crate::model::{CurrentResourceLimits, ExecutionStatus, InterruptKind, WorkerConfig}; use crate::services::active_workers::ActiveWorkers; @@ -46,11 +46,12 @@ use async_trait::async_trait; use bincode::{Decode, Encode}; use bytes::Bytes; use cap_std::ambient_authority; +use golem_common::model::oplog::{OplogEntry, WrappedFunctionType}; +use golem_common::model::regions::{DeletedRegions, OplogRegion}; use golem_common::model::{ AccountId, CallingConvention, InvocationKey, PromiseId, Timestamp, VersionedWorkerId, WorkerId, WorkerMetadata, WorkerStatus, }; -use golem_common::model::{OplogEntry, WrappedFunctionType}; use golem_wasm_rpc::wasmtime::ResourceStore; use golem_wasm_rpc::{Uri, Value}; use serde::de::DeserializeOwned; @@ -73,6 +74,7 @@ use crate::services::rpc::Rpc; use crate::services::scheduler::SchedulerService; use crate::services::HasOplogService; use crate::wasi_host; +use crate::worker::calculate_last_known_status; pub mod blobstore; mod cli; @@ -119,6 +121,10 @@ impl DurableWorkerCtx { self.private_state.commit_oplog().await } + async fn get_oplog_entry_marker(&mut self) -> Result<(), GolemError> { + self.private_state.get_oplog_entry_marker().await + } + async fn get_oplog_entry_imported_function_invoked<'de, R>(&mut self) -> Result where R: Decode + DeserializeOwned, @@ -206,8 +212,7 @@ impl DurableWorkerCtx { |e| GolemError::runtime(format!("Failed to create temporary directory: {e}")), )?); debug!( - "Created temporary file system root for {:?} at {:?}", - worker_id, + "Created temporary file system root for {worker_id} at {:?}", temp_dir.path() ); let root_dir = cap_std::fs::Dir::open_ambient_dir(temp_dir.path(), ambient_authority()) @@ -215,6 +220,11 @@ impl DurableWorkerCtx { let oplog_size = oplog_service.get_size(&worker_id.worker_id).await; + debug!( + "Worker {} initialized with deleted regions {}", + worker_id, worker_config.deleted_regions + ); + let stdio = ManagedStandardIo::new(worker_id.worker_id.clone(), invocation_key_service.clone()); let stdin = ManagedStdIn::from_standard_io(stdio.clone()).await; @@ -263,6 +273,10 @@ impl DurableWorkerCtx { rpc, resources: HashMap::new(), last_resource_id: 0, + deleted_regions: worker_config.deleted_regions.clone(), + next_deleted_region: worker_config + .deleted_regions + .find_next_deleted_region(0), }, temp_dir, execution_status, @@ -295,7 +309,7 @@ impl DurableWorkerCtx { DurableWorkerCtxWasiHttpView(self) } - pub async fn create_promise(&self, oplog_idx: i32) -> PromiseId { + pub async fn create_promise(&self, oplog_idx: u64) -> PromiseId { self.public_state .promise_service .create(&self.worker_id.worker_id, oplog_idx) @@ -373,7 +387,12 @@ impl DurableWorkerCtx { let oplog_idx = self.private_state.oplog_idx; self.private_state .worker_service - .update_status(&self.worker_id.worker_id, status, oplog_idx) + .update_status( + &self.worker_id.worker_id, + status, + self.private_state.deleted_regions.clone(), + oplog_idx, + ) .await } @@ -718,8 +737,9 @@ impl InvocationHooks for DurableWorkerCtx { let is_interrupt = is_interrupt(error); let is_suspend = is_suspend(error); + let is_jump = is_jump(error); - if is_live_after && !is_interrupt && !is_suspend { + if is_live_after && !is_interrupt && !is_suspend && !is_jump { self.set_oplog_entry(OplogEntry::Error { timestamp: Timestamp::now_utc(), }) @@ -750,12 +770,16 @@ impl InvocationHooks for DurableWorkerCtx { let is_interrupt = is_interrupt(error); let is_suspend = is_suspend(error); + let is_jump = is_jump(error); + match decision { RecoveryDecision::None => { if is_interrupt { Ok(WorkerStatus::Interrupted) } else if is_suspend { Ok(WorkerStatus::Suspended) + } else if is_jump { + Ok(WorkerStatus::Running) } else { Ok(WorkerStatus::Failed) } @@ -804,7 +828,10 @@ impl InvocationHooks for DurableWorkerCtx { } } - debug!("Function finished with {:?}", output); + debug!( + "Function {}/{full_function_name} finished with {:?}", + self.worker_id, output + ); // Return indicating that it is done Ok(Some(output)) @@ -843,17 +870,23 @@ impl> ExternalOperations for Dur this: &T, worker_id: &WorkerId, status: WorkerStatus, - ) { - let last_oplog_idx = last_oplog_idx(this, worker_id).await; + ) -> Result<(), GolemError> { + let latest_status = calculate_last_known_status(this, worker_id).await?; this.worker_service() - .update_status(worker_id, status, last_oplog_idx) + .update_status( + worker_id, + status, + latest_status.deleted_regions, + latest_status.oplog_idx, + ) .await; + Ok(()) } async fn get_worker_retry_count + Send + Sync>( this: &T, worker_id: &WorkerId, - ) -> u32 { + ) -> u64 { trailing_error_count(this, worker_id).await } @@ -884,7 +917,7 @@ impl> ExternalOperations for Dur instance: &Instance, store: &mut (impl AsContextMut + Send), ) -> Result<(), GolemError> { - debug!("Starting prepare_instance for {}", worker_id); + debug!("Starting prepare_instance for {worker_id}"); let start = Instant::now(); let mut count = 0; let result = loop { @@ -944,7 +977,7 @@ impl> ExternalOperations for Dur }; record_resume_worker(start.elapsed()); record_number_of_replayed_functions(count); - debug!("Finished prepare_instance for {}", worker_id); + debug!("Finished prepare_instance for {worker_id}"); result } @@ -967,14 +1000,14 @@ impl> ExternalOperations for Dur async fn on_shard_assignment_changed + Send + Sync>( this: &T, ) -> Result<(), anyhow::Error> { - info!("Recovering instances"); + info!("Recovering workers"); - let instances = this.worker_service().get_running_workers_in_shards().await; + let workers = this.worker_service().get_running_workers_in_shards().await; - debug!("Recovering running instances: {:?}", instances); + debug!("Recovering running workers: {:?}", workers); - for instance in instances { - let worker_id = instance.worker_id; + for worker in workers { + let worker_id = worker.worker_id; let previous_tries = Self::get_worker_retry_count(this, &worker_id.worker_id).await; let decision = this .recovery_management() @@ -986,16 +1019,16 @@ impl> ExternalOperations for Dur ); } - info!("Finished recovering instances"); + info!("Finished recovering workers"); Ok(()) } } -async fn last_oplog_idx(this: &T, worker_id: &WorkerId) -> i32 { +async fn last_oplog_idx(this: &T, worker_id: &WorkerId) -> u64 { this.oplog_service().get_size(worker_id).await } -async fn trailing_error_count(this: &T, worker_id: &WorkerId) -> u32 { +async fn trailing_error_count(this: &T, worker_id: &WorkerId) -> u64 { let mut idx = this.oplog_service().get_size(worker_id).await; let mut count = 0; if idx == 0 { @@ -1022,8 +1055,8 @@ async fn trailing_error_count(this: &T, worker_id: &WorkerId pub struct PrivateDurableWorkerState { buffer: VecDeque, - oplog_idx: i32, - oplog_size: i32, + oplog_idx: u64, + oplog_size: u64, oplog_service: Arc, promise_service: Arc, scheduler_service: Arc, @@ -1040,6 +1073,8 @@ pub struct PrivateDurableWorkerState { pub rpc: Arc, resources: HashMap, last_resource_id: u64, + deleted_regions: DeletedRegions, + next_deleted_region: Option, } impl PrivateDurableWorkerState { @@ -1053,11 +1088,11 @@ impl PrivateDurableWorkerState { self.oplog_service.append(worker_id, &arrays).await } - pub async fn get_oplog_size(&mut self) -> i32 { + pub async fn get_oplog_size(&mut self) -> u64 { self.oplog_service.get_size(&self.worker_id).await } - pub async fn read_oplog(&self, idx: i32, n: i32) -> Vec { + pub async fn read_oplog(&self, idx: u64, n: u64) -> Vec { self.oplog_service.read(&self.worker_id, idx, n).await } @@ -1083,12 +1118,55 @@ impl PrivateDurableWorkerState { pub async fn get_oplog_entry(&mut self) -> OplogEntry { assert!(self.is_replay()); + let oplog_entries = self.read_oplog(self.oplog_idx, 1).await; let oplog_entry = oplog_entries[0].clone(); - self.oplog_idx += 1; + + let update_next_deleted_region = match &self.next_deleted_region { + Some(region) if region.start == self.oplog_idx => { + let target = region.end + 1; + debug!( + "Worker {} reached deleted region at {}, jumping to {} (oplog size: {})", + self.worker_id, self.oplog_idx, target, self.oplog_size + ); + self.oplog_idx = target; + true + } + _ => { + self.oplog_idx += 1; + false + } + }; + + if update_next_deleted_region { + self.next_deleted_region = self + .deleted_regions + .find_next_deleted_region(self.oplog_idx); + } + oplog_entry } + async fn get_oplog_entry_marker(&mut self) -> Result<(), GolemError> { + loop { + let oplog_entry = self.get_oplog_entry().await; + match oplog_entry { + OplogEntry::NoOp { .. } => { + break Ok(()); + } + OplogEntry::Suspend { .. } => (), + OplogEntry::Error { .. } => (), + OplogEntry::Debug { message, .. } => debug!("Debug: {}", message), + _ => { + break Err(GolemError::unexpected_oplog_entry( + "NoOp", + format!("{:?}", oplog_entry), + )); + } + } + } + } + async fn get_oplog_entry_imported_function_invoked<'de, R>(&mut self) -> Result where R: Decode + DeserializeOwned, @@ -1212,7 +1290,7 @@ impl PrivateDurableWorkerState { } /// Consumes Suspend and Error entries which are hints for the server to decide whether to - /// keep workers in memory or allow them to rerun etc but contain no actionable information + /// keep workers in memory or allow them to rerun etc., but contain no actionable information /// during replay async fn consume_hint_entries(&mut self) { loop { @@ -1275,7 +1353,7 @@ impl PrivateDurableWorkerState { } /// Counts the number of Error entries that are at the end of the oplog. This equals to the number of retries that have been attempted. - pub async fn trailing_error_count(&self) -> u32 { + pub async fn trailing_error_count(&self) -> u64 { trailing_error_count(self, &self.worker_id).await } } diff --git a/golem-worker-executor-base/src/durable_host/random/insecure.rs b/golem-worker-executor-base/src/durable_host/random/insecure.rs index 99096d7e85..fc694a9145 100644 --- a/golem-worker-executor-base/src/durable_host/random/insecure.rs +++ b/golem-worker-executor-base/src/durable_host/random/insecure.rs @@ -18,7 +18,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::random::insecure::Host; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/random/insecure_seed.rs b/golem-worker-executor-base/src/durable_host/random/insecure_seed.rs index 4f4a738434..bb3e72f149 100644 --- a/golem-worker-executor-base/src/durable_host/random/insecure_seed.rs +++ b/golem-worker-executor-base/src/durable_host/random/insecure_seed.rs @@ -18,7 +18,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::random::insecure_seed::Host; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/random/random.rs b/golem-worker-executor-base/src/durable_host/random/random.rs index 3bb3dca9ac..a0e809aa73 100644 --- a/golem-worker-executor-base/src/durable_host/random/random.rs +++ b/golem-worker-executor-base/src/durable_host/random/random.rs @@ -18,7 +18,7 @@ use crate::durable_host::serialized::SerializableError; use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::wasi::random::random::Host; #[async_trait] diff --git a/golem-worker-executor-base/src/durable_host/serialized.rs b/golem-worker-executor-base/src/durable_host/serialized.rs index ce4f3b2281..bb57672258 100644 --- a/golem-worker-executor-base/src/durable_host/serialized.rs +++ b/golem-worker-executor-base/src/durable_host/serialized.rs @@ -595,7 +595,7 @@ mod tests { } fn promiseid_strat() -> impl Strategy { - (workerid_strat(), any::()).prop_map(|(worker_id, oplog_idx)| PromiseId { + (workerid_strat(), any::()).prop_map(|(worker_id, oplog_idx)| PromiseId { worker_id, oplog_idx, }) diff --git a/golem-worker-executor-base/src/durable_host/sockets/ip_name_lookup.rs b/golem-worker-executor-base/src/durable_host/sockets/ip_name_lookup.rs index 6024b375ea..e83cfee343 100644 --- a/golem-worker-executor-base/src/durable_host/sockets/ip_name_lookup.rs +++ b/golem-worker-executor-base/src/durable_host/sockets/ip_name_lookup.rs @@ -19,7 +19,7 @@ use crate::durable_host::serialized::{SerializableError, SerializableIpAddresses use crate::durable_host::{Durability, DurableWorkerCtx}; use crate::metrics::wasm::record_host_function_call; use crate::workerctx::WorkerCtx; -use golem_common::model::WrappedFunctionType; +use golem_common::model::oplog::WrappedFunctionType; use wasmtime_wasi::preview2::bindings::sockets::network::ErrorCode; use wasmtime_wasi::preview2::bindings::wasi::sockets::ip_name_lookup::{ Host, HostResolveAddressStream, IpAddress, Network, Pollable, ResolveAddressStream, diff --git a/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs b/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs index 72141b4963..95b14fec9d 100644 --- a/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs +++ b/golem-worker-executor-base/src/durable_host/wasm_rpc/mod.rs @@ -19,7 +19,8 @@ use crate::services::rpc::{RpcDemand, RpcError}; use crate::workerctx::WorkerCtx; use anyhow::anyhow; use async_trait::async_trait; -use golem_common::model::{TemplateId, WorkerId, WrappedFunctionType}; +use golem_common::model::oplog::WrappedFunctionType; +use golem_common::model::{TemplateId, WorkerId}; use golem_wasm_rpc::golem::rpc::types::Uri; use golem_wasm_rpc::{HostWasmRpc, WasmRpcEntry, WitValue}; use std::str::FromStr; @@ -82,11 +83,11 @@ impl HostWasmRpc for DurableWorkerCtx { match result { Ok(result) => { - debug!("RPC result: {result:?}"); + debug!("RPC result for {}: {result:?}", self.worker_id); Ok(Ok(result)) } Err(err) => { - error!("RPC error: {err}"); + error!("RPC error for {}: {err}", self.worker_id); match err { RpcError::ProtocolError { details } => { Ok(Err(golem_wasm_rpc::RpcError::ProtocolError(details))) diff --git a/golem-worker-executor-base/src/error.rs b/golem-worker-executor-base/src/error.rs index d4ba131ea8..e5bf270bea 100644 --- a/golem-worker-executor-base/src/error.rs +++ b/golem-worker-executor-base/src/error.rs @@ -740,3 +740,10 @@ pub fn is_suspend(error: &anyhow::Error) -> bool { .downcast_ref::() .map_or(false, |kind| *kind == InterruptKind::Suspend) } + +pub fn is_jump(error: &anyhow::Error) -> bool { + error + .root_cause() + .downcast_ref::() + .map_or(false, |kind| *kind == InterruptKind::Jump) +} diff --git a/golem-worker-executor-base/src/grpc.rs b/golem-worker-executor-base/src/grpc.rs index f9f16ec99f..4a621839e5 100644 --- a/golem-worker-executor-base/src/grpc.rs +++ b/golem-worker-executor-base/src/grpc.rs @@ -172,7 +172,6 @@ impl + UsesAllDeps + Send + Sync + worker_id: &common_model::WorkerId, metadata: &Option, ) -> Result { - debug!("validate_worker_status for {worker_id}"); let worker_status = Ctx::get_assumed_worker_status(self, worker_id, metadata).await; match worker_status { @@ -410,11 +409,11 @@ impl + UsesAllDeps + Send + Sync + } WorkerStatus::Suspended => { debug!("Marking suspended worker {worker_id} as interrupted"); - Ctx::set_worker_status(self, &worker_id, WorkerStatus::Interrupted).await; + Ctx::set_worker_status(self, &worker_id, WorkerStatus::Interrupted).await?; } WorkerStatus::Retrying => { debug!("Marking worker {worker_id} scheduled to be retried as interrupted"); - Ctx::set_worker_status(self, &worker_id, WorkerStatus::Interrupted).await; + Ctx::set_worker_status(self, &worker_id, WorkerStatus::Interrupted).await?; } WorkerStatus::Running => { let worker_state = Worker::get_or_create_with_config( diff --git a/golem-worker-executor-base/src/invocation.rs b/golem-worker-executor-base/src/invocation.rs index a4be6447ea..2dedc93438 100644 --- a/golem-worker-executor-base/src/invocation.rs +++ b/golem-worker-executor-base/src/invocation.rs @@ -22,7 +22,7 @@ use tracing::{debug, error, warn}; use wasmtime::component::{Func, Val}; use wasmtime::{AsContextMut, StoreContextMut}; -use crate::error::{is_interrupt, is_suspend, GolemError}; +use crate::error::{is_interrupt, is_jump, is_suspend, GolemError}; use crate::metrics::wasm::{record_invocation, record_invocation_consumption}; use crate::workerctx::{FuelManagement, WorkerCtx}; @@ -49,14 +49,12 @@ pub async fn invoke_worker( let mut store = store.as_context_mut(); let worker_id = store.data().worker_id().clone(); - debug!("invoke_worker_impl: {worker_id}/{full_function_name}"); + debug!("invoke_worker: {worker_id}/{full_function_name}"); if let Some(invocation_key) = &store.data().get_current_invocation_key().await { store.data_mut().resume_invocation_key(invocation_key).await } - debug!("invoke_worker_impl_or_fail: {worker_id}/{full_function_name}"); - let result = invoke_or_fail( &worker_id, full_function_name.clone(), @@ -69,7 +67,7 @@ pub async fn invoke_worker( .await; debug!( - "invoke_worker_impl_or_fail: {worker_id}/{full_function_name} resulted in {:?}", + "invoke_or_fail: {worker_id}/{full_function_name} resulted in {:?}", result ); @@ -81,7 +79,7 @@ pub async fn invoke_worker( match invocation_key { Some(invocation_key) => { debug!( - "Storing interrupted status for invocation key {:?}", + "Storing interrupted status for invocation key {:?} in {worker_id}/{full_function_name}", &invocation_key ); store @@ -99,12 +97,16 @@ pub async fn invoke_worker( // this invocation was suspended and expected to be resumed by an external call or schedule record_invocation(was_live_before, "suspended"); false + } else if is_jump(&err) { + // the worker needs to be restarted in order to jump to the past, but otherwise continues running + record_invocation(was_live_before, "jump"); + false } else { // this invocation failed it won't be retried later match invocation_key { Some(invocation_key) => { debug!( - "Storing failed result for invocation key {:?}", + "Storing failed result for invocation key {:?} in {worker_id}/{full_function_name}", &invocation_key ); store @@ -300,7 +302,10 @@ async fn invoke( call_exported_function(&mut store, function, params, context).await?; for resource in resources_to_drop { - debug!("Dropping passed owned resources {:?}", resource); + debug!( + "Dropping passed owned resources {:?} in {context}", + resource + ); resource.resource_drop_async(&mut store).await?; } @@ -313,7 +318,7 @@ async fn invoke( output.push(result_value); } - store_results(&mut store, &output).await; + store_results(&mut store, &output, context).await; Ok(InvokeResult { exited: false, @@ -349,7 +354,7 @@ async fn invoke( let stdout = store.data_mut().finish_capturing_stdout().await.ok(); let output: Vec = vec![Value::String(stdout.unwrap_or("".to_string()))]; - store_results(&mut store, &output).await; + store_results(&mut store, &output, context).await; Ok(InvokeResult { exited, @@ -360,10 +365,14 @@ async fn invoke( } } -async fn store_results<'a, Ctx: WorkerCtx>(store: &mut StoreContextMut<'a, Ctx>, output: &[Value]) { +async fn store_results<'a, Ctx: WorkerCtx>( + store: &mut StoreContextMut<'a, Ctx>, + output: &[Value], + context: &str, +) { if let Some(invocation_key) = store.data().get_current_invocation_key().await { debug!( - "Storing successful results for invocation key {:?}", + "Storing successful results for invocation key {:?} in {context}", &invocation_key ); @@ -372,7 +381,7 @@ async fn store_results<'a, Ctx: WorkerCtx>(store: &mut StoreContextMut<'a, Ctx>, .confirm_invocation_key(&invocation_key, Ok(output.to_vec())) .await; } else { - debug!("No invocation key"); + debug!("No invocation key for {context}"); } } @@ -414,7 +423,7 @@ async fn drop_resource( } let output = Vec::new(); - store_results(&mut store, &output).await; + store_results(&mut store, &output, context).await; Ok(InvokeResult { exited: false, @@ -456,7 +465,10 @@ async fn call_exported_function( .data_mut() .return_fuel(current_fuel_level as i64) .await?; - debug!("fuel consumed for call: {}", consumed_fuel_for_call); + debug!( + "fuel consumed for call {context}: {}", + consumed_fuel_for_call + ); record_invocation_consumption(consumed_fuel_for_call); Ok((result.map(|_| results), consumed_fuel_for_call)) diff --git a/golem-worker-executor-base/src/model.rs b/golem-worker-executor-base/src/model.rs index 3c45c414af..a0d0df76be 100644 --- a/golem-worker-executor-base/src/model.rs +++ b/golem-worker-executor-base/src/model.rs @@ -17,6 +17,7 @@ use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::sync::Arc; +use golem_common::model::regions::DeletedRegions; use golem_common::model::{ShardAssignment, ShardId, VersionedWorkerId, WorkerId}; use serde::{Deserialize, Serialize}; @@ -45,6 +46,7 @@ pub enum InterruptKind { Interrupt, Restart, Suspend, + Jump, } impl Display for InterruptKind { @@ -53,18 +55,20 @@ impl Display for InterruptKind { InterruptKind::Interrupt => write!(f, "Interrupted via the Golem API"), InterruptKind::Restart => write!(f, "Simulated crash via the Golem API"), InterruptKind::Suspend => write!(f, "Suspended"), + InterruptKind::Jump => write!(f, "Jumping back in time"), } } } impl Error for InterruptKind {} -/// Worker-specific configuration. These values are used to initialize the worker and they can +/// Worker-specific configuration. These values are used to initialize the worker, and they can /// be different for each worker. #[derive(Clone, Debug)] pub struct WorkerConfig { pub args: Vec, pub env: Vec<(String, String)>, + pub deleted_regions: DeletedRegions, } impl WorkerConfig { @@ -72,6 +76,7 @@ impl WorkerConfig { versioned_worker_id: VersionedWorkerId, worker_args: Vec, mut worker_env: Vec<(String, String)>, + deleted_regions: DeletedRegions, ) -> WorkerConfig { let worker_name = versioned_worker_id.worker_id.worker_name.clone(); let template_id = versioned_worker_id.worker_id.template_id; @@ -82,6 +87,7 @@ impl WorkerConfig { WorkerConfig { args: worker_args, env: worker_env, + deleted_regions, } } } diff --git a/golem-worker-executor-base/src/preview2/mod.rs b/golem-worker-executor-base/src/preview2/mod.rs index 68ec0c84a1..7ee3dc10a7 100644 --- a/golem-worker-executor-base/src/preview2/mod.rs +++ b/golem-worker-executor-base/src/preview2/mod.rs @@ -15,7 +15,7 @@ wasmtime::component::bindgen!({ path: "golem-wit/wit", interfaces: " - import golem:api/host; + import golem:api/host@0.2.0; import wasi:blobstore/blobstore; import wasi:blobstore/container; diff --git a/golem-worker-executor-base/src/services/compiled_template.rs b/golem-worker-executor-base/src/services/compiled_template.rs index e7a2efbfd7..e09348b0ed 100644 --- a/golem-worker-executor-base/src/services/compiled_template.rs +++ b/golem-worker-executor-base/src/services/compiled_template.rs @@ -62,7 +62,10 @@ pub async fn configured( aws_config::defaults(BehaviorVersion::v2023_11_09()).region(Region::new(region)); let sdk_config = if let Some(endpoint_url) = &config.aws_endpoint_url { - info!("The endpoint urls is {}", &endpoint_url); + info!( + "The AWS endpoint urls for compiled template service is {}", + &endpoint_url + ); sdk_config_base.endpoint_url(endpoint_url).load().await } else { sdk_config_base.load().await diff --git a/golem-worker-executor-base/src/services/invocation_key.rs b/golem-worker-executor-base/src/services/invocation_key.rs index e9185b79ea..d74c29d2c1 100644 --- a/golem-worker-executor-base/src/services/invocation_key.rs +++ b/golem-worker-executor-base/src/services/invocation_key.rs @@ -212,7 +212,7 @@ impl InvocationKeyService for InvocationKeyServiceDefault { worker_id: &WorkerId, key: &InvocationKey, ) -> LookupResult { - debug!("wait_for_confirmation {key:?}"); + debug!("wait_for_confirmation for {worker_id}: {key:?}"); loop { match self.lookup_key(worker_id, key) { LookupResult::Invalid => break LookupResult::Invalid, diff --git a/golem-worker-executor-base/src/services/oplog.rs b/golem-worker-executor-base/src/services/oplog.rs index d1d411f8fb..59ce0f65e0 100644 --- a/golem-worker-executor-base/src/services/oplog.rs +++ b/golem-worker-executor-base/src/services/oplog.rs @@ -19,7 +19,7 @@ use bytes::Bytes; use fred::prelude::RedisValue; use fred::types::RedisKey; use golem_common::metrics::redis::record_redis_serialized_size; -use golem_common::model::OplogEntry; +use golem_common::model::oplog::OplogEntry; use golem_common::model::WorkerId; use golem_common::redis::RedisPool; @@ -29,11 +29,11 @@ use crate::metrics::oplog::record_oplog_call; pub trait OplogService { async fn append(&self, worker_id: &WorkerId, arrays: &[OplogEntry]); - async fn get_size(&self, worker_id: &WorkerId) -> i32; + async fn get_size(&self, worker_id: &WorkerId) -> u64; async fn delete(&self, worker_id: &WorkerId); - async fn read(&self, worker_id: &WorkerId, idx: i32, n: i32) -> Vec; + async fn read(&self, worker_id: &WorkerId, idx: u64, n: u64) -> Vec; } #[derive(Clone, Debug)] @@ -109,7 +109,7 @@ impl OplogService for OplogServiceDefault { } } - async fn get_size(&self, worker_id: &WorkerId) -> i32 { + async fn get_size(&self, worker_id: &WorkerId) -> u64 { record_oplog_call("get_size"); let key = get_oplog_redis_key(worker_id); @@ -137,7 +137,7 @@ impl OplogService for OplogServiceDefault { }); } - async fn read(&self, worker_id: &WorkerId, idx: i32, n: i32) -> Vec { + async fn read(&self, worker_id: &WorkerId, idx: u64, n: u64) -> Vec { record_oplog_call("read"); let key = get_oplog_redis_key(worker_id); @@ -200,7 +200,7 @@ impl OplogService for OplogServiceMock { unimplemented!() } - async fn get_size(&self, _worker_id: &WorkerId) -> i32 { + async fn get_size(&self, _worker_id: &WorkerId) -> u64 { unimplemented!() } @@ -208,7 +208,7 @@ impl OplogService for OplogServiceMock { unimplemented!() } - async fn read(&self, _worker_id: &WorkerId, _idx: i32, _n: i32) -> Vec { + async fn read(&self, _worker_id: &WorkerId, _idx: u64, _n: u64) -> Vec { unimplemented!() } } diff --git a/golem-worker-executor-base/src/services/promise.rs b/golem-worker-executor-base/src/services/promise.rs index 650c56fea4..41ad5faa41 100644 --- a/golem-worker-executor-base/src/services/promise.rs +++ b/golem-worker-executor-base/src/services/promise.rs @@ -37,7 +37,7 @@ use crate::services::golem_config::PromisesConfig; /// Service implementing creation, completion and polling of promises #[async_trait] pub trait PromiseService { - async fn create(&self, worker_id: &WorkerId, oplog_idx: i32) -> PromiseId; + async fn create(&self, worker_id: &WorkerId, oplog_idx: u64) -> PromiseId; async fn wait_for(&self, promise_id: PromiseId) -> Result, GolemError>; @@ -90,7 +90,7 @@ impl PromiseServiceRedis { #[async_trait] impl PromiseService for PromiseServiceRedis { - async fn create(&self, worker_id: &WorkerId, oplog_idx: i32) -> PromiseId { + async fn create(&self, worker_id: &WorkerId, oplog_idx: u64) -> PromiseId { let promise_id = PromiseId { worker_id: worker_id.clone(), oplog_idx, @@ -294,7 +294,7 @@ impl PromiseServiceInMemory { #[async_trait] impl PromiseService for PromiseServiceInMemory { - async fn create(&self, worker_id: &WorkerId, oplog_idx: i32) -> PromiseId { + async fn create(&self, worker_id: &WorkerId, oplog_idx: u64) -> PromiseId { let promise_id = PromiseId { worker_id: worker_id.clone(), oplog_idx, @@ -382,7 +382,7 @@ impl PromiseServiceMock { #[cfg(any(feature = "mocks", test))] #[async_trait] impl PromiseService for PromiseServiceMock { - async fn create(&self, _worker_id: &WorkerId, _oplog_idx: i32) -> PromiseId { + async fn create(&self, _worker_id: &WorkerId, _oplog_idx: u64) -> PromiseId { unimplemented!() } diff --git a/golem-worker-executor-base/src/services/recovery.rs b/golem-worker-executor-base/src/services/recovery.rs index ada4c98f1a..014c79958f 100644 --- a/golem-worker-executor-base/src/services/recovery.rs +++ b/golem-worker-executor-base/src/services/recovery.rs @@ -48,13 +48,13 @@ pub trait RecoveryManagement { async fn schedule_recovery_for_error( &self, worker_id: &VersionedWorkerId, - previous_tries: u32, + previous_tries: u64, current_error: &anyhow::Error, ) -> RecoveryDecision; async fn schedule_recovery_on_startup( &self, worker_id: &VersionedWorkerId, - previous_tries: u32, + previous_tries: u64, ) -> RecoveryDecision; } @@ -280,13 +280,14 @@ impl RecoveryManagementDefault { fn get_recovery_decision_for_error( &self, - previous_tries: u32, + previous_tries: u64, current_error: &anyhow::Error, ) -> RecoveryDecision { match current_error.root_cause().downcast_ref::() { Some(InterruptKind::Interrupt) => RecoveryDecision::None, Some(InterruptKind::Suspend) => RecoveryDecision::None, Some(InterruptKind::Restart) => RecoveryDecision::Immediate, + Some(InterruptKind::Jump) => RecoveryDecision::Immediate, None => match Ctx::is_exit(current_error) { Some(_) => RecoveryDecision::None, None => match current_error.root_cause().downcast_ref::() { @@ -307,8 +308,8 @@ impl RecoveryManagementDefault { } } - fn get_recovery_decision_on_startup(&self, previous_tries: u32) -> RecoveryDecision { - if previous_tries < self.golem_config.retry.max_attempts { + fn get_recovery_decision_on_startup(&self, previous_tries: u64) -> RecoveryDecision { + if previous_tries < (self.golem_config.retry.max_attempts as u64) { RecoveryDecision::Immediate } else { RecoveryDecision::None @@ -401,7 +402,7 @@ impl RecoveryManagement for RecoveryManagementDefault { async fn schedule_recovery_for_error( &self, worker_id: &VersionedWorkerId, - previous_tries: u32, + previous_tries: u64, current_error: &anyhow::Error, ) -> RecoveryDecision { self.schedule_recovery( @@ -414,7 +415,7 @@ impl RecoveryManagement for RecoveryManagementDefault { async fn schedule_recovery_on_startup( &self, worker_id: &VersionedWorkerId, - previous_tries: u32, + previous_tries: u64, ) -> RecoveryDecision { self.schedule_recovery( worker_id, @@ -476,7 +477,7 @@ impl RecoveryManagement for RecoveryManagementMock { async fn schedule_recovery_for_error( &self, _worker_id: &VersionedWorkerId, - _previous_tries: u32, + _previous_tries: u64, _current_error: &anyhow::Error, ) -> RecoveryDecision { unimplemented!() @@ -485,7 +486,7 @@ impl RecoveryManagement for RecoveryManagementMock { async fn schedule_recovery_on_startup( &self, _worker_id: &VersionedWorkerId, - _previous_tries: u32, + _previous_tries: u64, ) -> RecoveryDecision { unimplemented!() } @@ -681,14 +682,14 @@ mod tests { _this: &T, _worker_id: &WorkerId, _status: WorkerStatus, - ) { + ) -> Result<(), GolemError> { unimplemented!() } async fn get_worker_retry_count + Send + Sync>( _this: &T, _worker_id: &WorkerId, - ) -> u32 { + ) -> u64 { unimplemented!() } diff --git a/golem-worker-executor-base/src/services/worker.rs b/golem-worker-executor-base/src/services/worker.rs index 76a314de46..7ec3032c24 100644 --- a/golem-worker-executor-base/src/services/worker.rs +++ b/golem-worker-executor-base/src/services/worker.rs @@ -19,9 +19,10 @@ use bytes::Bytes; use dashmap::DashMap; use fred::prelude::*; use golem_common::metrics::redis::record_redis_serialized_size; +use golem_common::model::regions::DeletedRegions; use golem_common::model::{ShardId, WorkerId, WorkerMetadata, WorkerStatus, WorkerStatusRecord}; use golem_common::redis::RedisPool; -use tracing::{debug, error}; +use tracing::debug; use crate::error::GolemError; use crate::metrics::workers::record_worker_call; @@ -41,7 +42,13 @@ pub trait WorkerService { async fn enumerate(&self) -> Vec; - async fn update_status(&self, worker_id: &WorkerId, status: WorkerStatus, oplog_idx: i32); + async fn update_status( + &self, + worker_id: &WorkerId, + status: WorkerStatus, + deleted_regions: DeletedRegions, + oplog_idx: u64, + ); } pub fn configured( @@ -113,7 +120,7 @@ impl WorkerService for WorkerServiceRedis { record_redis_serialized_size("instance", "details", details_value.len()); - let old_details_value: Option = self + let _ = self .redis .with("instance", "add") .set( @@ -127,23 +134,10 @@ impl WorkerService for WorkerServiceRedis { .unwrap_or_else(|err| { panic!("failed to set worker metadata for {details_key} in Redis: {err}") }); - - let metadata = match old_details_value { - Some(old_details_value) => { - if details_value == old_details_value { - Ok(()) - } else { - error!("Trying to activate existing worker {worker_id} with different metadata: {:?} vs {:?}", old_details_value, details_value); - Err(GolemError::WorkerAlreadyExists { - worker_id: worker_id.clone(), - }) - } - } - None => { - debug!("add_worker for {worker_id} succeeded"); - Ok(()) - } - }; + // NOTE: we used to check here if the old and new metadata values are equivalent but this no longer works + // because of the worker status record which may be recalculated from latest oplog entries. This + // is not a problem though because soon by https://github.com/golemcloud/golem/issues/238 the static + // part will no longer be stored in Redis. let status_key = get_worker_status_redis_key(worker_id); let status_value = self @@ -204,7 +198,7 @@ impl WorkerService for WorkerServiceRedis { ) }); - metadata + Ok(()) } async fn get(&self, worker_id: &WorkerId) -> Option { @@ -325,12 +319,19 @@ impl WorkerService for WorkerServiceRedis { self.enum_workers_at_key(key).await } - async fn update_status(&self, worker_id: &WorkerId, status: WorkerStatus, oplog_idx: i32) { + async fn update_status( + &self, + worker_id: &WorkerId, + status: WorkerStatus, + deleted_regions: DeletedRegions, + oplog_idx: u64, + ) { record_worker_call("update_status"); let status_key = get_worker_status_redis_key(worker_id); let status_value = WorkerStatusRecord { status: status.clone(), + deleted_regions, oplog_idx, }; let serialized_status_value = self.redis.serialize(&status_value).unwrap_or_else(|err| { @@ -458,9 +459,19 @@ impl WorkerService for WorkerServiceInMemory { self.workers.iter().map(|i| i.clone()).collect() } - async fn update_status(&self, worker_id: &WorkerId, status: WorkerStatus, oplog_idx: i32) { + async fn update_status( + &self, + worker_id: &WorkerId, + status: WorkerStatus, + deleted_regions: DeletedRegions, + oplog_idx: u64, + ) { self.workers.entry(worker_id.clone()).and_modify(|worker| { - worker.last_known_status = WorkerStatusRecord { status, oplog_idx } + worker.last_known_status = WorkerStatusRecord { + status, + deleted_regions, + oplog_idx, + } }); } } @@ -505,7 +516,13 @@ impl WorkerService for WorkerServiceMock { unimplemented!() } - async fn update_status(&self, _worker_id: &WorkerId, _status: WorkerStatus, _oplog_idx: i32) { + async fn update_status( + &self, + _worker_id: &WorkerId, + _status: WorkerStatus, + _deleted_regions: DeletedRegions, + _oplog_idx: u64, + ) { unimplemented!() } } diff --git a/golem-worker-executor-base/src/wasi_host/mod.rs b/golem-worker-executor-base/src/wasi_host/mod.rs index 2652705613..66ac89d2bd 100644 --- a/golem-worker-executor-base/src/wasi_host/mod.rs +++ b/golem-worker-executor-base/src/wasi_host/mod.rs @@ -17,7 +17,6 @@ use std::time::Duration; use cap_std::fs::Dir; -use tracing::debug; use wasmtime::component::Linker; use wasmtime::Engine; use wasmtime_wasi::preview2::bindings::wasi; @@ -139,7 +138,6 @@ where F: FnOnce(WasiCtx, ResourceTable) -> T, { let table = ResourceTable::new(); - debug!("Creating WASI context, root directory is {:?}", root_dir); let wasi = WasiCtxBuilder::new() .args(args) .envs(env) diff --git a/golem-worker-executor-base/src/worker.rs b/golem-worker-executor-base/src/worker.rs index dbee94ee98..8587f30e2e 100644 --- a/golem-worker-executor-base/src/worker.rs +++ b/golem-worker-executor-base/src/worker.rs @@ -20,9 +20,11 @@ use std::time::Instant; use async_mutex::Mutex; use bytes::Bytes; use golem_common::cache::PendingOrFinal; +use golem_common::model::oplog::OplogEntry; +use golem_common::model::regions::{DeletedRegions, DeletedRegionsBuilder}; use golem_common::model::{ AccountId, CallingConvention, InvocationKey, VersionedWorkerId, WorkerId, WorkerMetadata, - WorkerStatusRecord, + WorkerStatus, WorkerStatusRecord, }; use golem_wasm_rpc::Value; use tokio::sync::broadcast::Receiver; @@ -36,7 +38,7 @@ use crate::model::{ExecutionStatus, InterruptKind, WorkerConfig}; use crate::services::golem_config::GolemConfig; use crate::services::invocation_key::LookupResult; use crate::services::worker_event::{WorkerEventService, WorkerEventServiceDefault}; -use crate::services::{HasAll, HasInvocationKeyService}; +use crate::services::{HasAll, HasInvocationKeyService, HasOplogService, HasWorkerService}; use crate::workerctx::{PublicWorkerIo, WorkerCtx}; /// Worker is one active wasmtime instance representing a Golem worker with its corresponding @@ -114,7 +116,11 @@ impl Worker { args: worker_args.clone(), env: worker_env.clone(), account_id, - last_known_status: WorkerStatusRecord::default(), + last_known_status: calculate_last_known_status( + this, + &versioned_worker_id.worker_id, + ) + .await?, }; this.worker_service().add(&worker_metadata).await?; @@ -137,7 +143,12 @@ impl Worker { this.rpc(), this.extra_deps(), this.config(), - WorkerConfig::new(worker_metadata.worker_id.clone(), worker_args, worker_env), + WorkerConfig::new( + worker_metadata.worker_id.clone(), + worker_args, + worker_env, + worker_metadata.last_known_status.deleted_regions.clone(), + ), execution_status.clone(), ) .await?; @@ -146,10 +157,11 @@ impl Worker { let mut store = Store::new(&this.engine(), context); store.set_epoch_deadline(this.config().limits.epoch_ticks); - store.epoch_deadline_callback(|mut store| { + let worker_id_clone = versioned_worker_id.worker_id.clone(); + store.epoch_deadline_callback(move |mut store| { let current_level = store.get_fuel().unwrap_or(0); if store.data().is_out_of_fuel(current_level as i64) { - debug!("ran out of fuel, borrowing more"); + debug!("{worker_id_clone} ran out of fuel, borrowing more"); store.data_mut().borrow_fuel_sync(); } @@ -167,7 +179,7 @@ impl Worker { let instance_pre = this.linker().instantiate_pre(&component).map_err(|e| { GolemError::worker_creation_failed( worker_id.clone(), - format!("Failed to pre-instantiate component: {e}"), + format!("Failed to pre-instantiate worker {worker_id}: {e}"), ) })?; @@ -177,7 +189,7 @@ impl Worker { .map_err(|e| { GolemError::worker_creation_failed( worker_id.clone(), - format!("Failed to instantiate component: {e}"), + format!("Failed to instantiate worker {worker_id}: {e}"), ) })?; @@ -595,8 +607,9 @@ pub async fn invoke_and_await( where T: HasInvocationKeyService, { + let worker_id = worker.metadata.worker_id.worker_id.clone(); match invoke( - worker.clone(), + worker, this, invocation_key.clone(), calling_convention, @@ -608,18 +621,20 @@ where Some(Ok(output)) => Ok(output), Some(Err(err)) => Err(err), None => { - let worker_id = &worker.metadata.worker_id.worker_id; let invocation_key = invocation_key.expect("missing invocation key for invoke-and-await"); - debug!("Waiting for invocation key {} to complete", invocation_key); + debug!( + "Waiting for invocation key {} to complete for {worker_id}", + invocation_key + ); let result = this .invocation_key_service() - .wait_for_confirmation(worker_id, &invocation_key) + .wait_for_confirmation(&worker_id, &invocation_key) .await; debug!( - "Invocation key {} lookup result: {:?}", + "Invocation key {} lookup result for {worker_id}: {:?}", invocation_key, result ); match result { @@ -636,3 +651,60 @@ where } } } + +/// Gets the last cached worker status record and the new oplog entries and calculates the new worker status. +pub async fn calculate_last_known_status( + this: &T, + worker_id: &WorkerId, +) -> Result +where + T: HasOplogService + HasWorkerService, +{ + let last_known = this + .worker_service() + .get(worker_id) + .await + .map(|metadata| metadata.last_known_status.clone()) + .unwrap_or_default(); + + let last_oplog_index = this.oplog_service().get_size(worker_id).await; + if last_known.oplog_idx == last_oplog_index { + Ok(last_known) + } else { + let new_entries = this + .oplog_service() + .read( + worker_id, + last_known.oplog_idx, + last_oplog_index - last_known.oplog_idx, + ) + .await; + + let status = calculate_latest_worker_status(&last_known.status, &new_entries); + let deleted_regions = calculate_deleted_regions(last_known.deleted_regions, &new_entries); + + Ok(WorkerStatusRecord { + oplog_idx: last_oplog_index, + status, + deleted_regions, + }) + } +} + +fn calculate_latest_worker_status( + _initial: &WorkerStatus, + _entries: &[OplogEntry], +) -> WorkerStatus { + // TODO: this is currently not 100% accurate because we don't have an Interrupted oplog entry, see https://github.com/golemcloud/golem/issues/239 + WorkerStatus::Running +} + +fn calculate_deleted_regions(initial: DeletedRegions, entries: &[OplogEntry]) -> DeletedRegions { + let mut builder = DeletedRegionsBuilder::from_regions(initial.into_regions()); + for entry in entries { + if let OplogEntry::Jump { jump, .. } = entry { + builder.add(jump.clone()); + } + } + builder.build() +} diff --git a/golem-worker-executor-base/src/workerctx.rs b/golem-worker-executor-base/src/workerctx.rs index c6de8efb83..c0d028c133 100644 --- a/golem-worker-executor-base/src/workerctx.rs +++ b/golem-worker-executor-base/src/workerctx.rs @@ -286,13 +286,13 @@ pub trait ExternalOperations { this: &T, worker_id: &WorkerId, status: WorkerStatus, - ); + ) -> Result<(), GolemError>; /// Gets how many times the worker has been retried to recover from an error. async fn get_worker_retry_count + Send + Sync>( this: &T, worker_id: &WorkerId, - ) -> u32; + ) -> u64; /// Gets a best-effort current worker status without activating the worker async fn get_assumed_worker_status + Send + Sync>( diff --git a/golem-worker-executor-base/tests/common/mod.rs b/golem-worker-executor-base/tests/common/mod.rs index d002ccd1e4..f232fad192 100644 --- a/golem-worker-executor-base/tests/common/mod.rs +++ b/golem-worker-executor-base/tests/common/mod.rs @@ -31,7 +31,8 @@ use golem_api_grpc::proto::golem::workerexecutor::{ InvokeWorkerRequest, ResumeWorkerRequest, }; use golem_common::model::{ - AccountId, InvocationKey, TemplateId, VersionedWorkerId, WorkerId, WorkerMetadata, WorkerStatus, + AccountId, InvocationKey, TemplateId, VersionedWorkerId, WorkerId, WorkerMetadata, + WorkerStatus, WorkerStatusRecord, }; use golem_worker_executor_base::error::GolemError; use golem_worker_executor_base::services::golem_config::{ @@ -72,11 +73,13 @@ use golem_worker_executor_base::workerctx::{ use golem_worker_executor_base::Bootstrap; use serde_json::Value as JsonValue; use tokio::runtime::Handle; +use tokio::select; use tokio::sync::mpsc::UnboundedReceiver; use tokio::task::JoinHandle; use golem::api; use golem_common::config::RedisConfig; +use golem_common::model::regions::DeletedRegions; use golem_worker_executor_base::preview2::golem; use golem_worker_executor_base::services::rpc::{ DirectWorkerInvocationRpc, RemoteInvocationRpc, Rpc, @@ -243,9 +246,29 @@ impl TestWorkerExecutor { match response.result { None => panic!("No response from connect_worker"), - Some(get_worker_metadata_response::Result::Success(metadata)) => { - Some(metadata.try_into().unwrap()) - } + Some(get_worker_metadata_response::Result::Success(metadata)) => Some(WorkerMetadata { + worker_id: VersionedWorkerId { + worker_id: metadata + .worker_id + .expect("no worker_id") + .clone() + .try_into() + .expect("invalid worker_id"), + template_version: metadata.template_version, + }, + args: metadata.args.clone(), + env: metadata + .env + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>(), + account_id: metadata.account_id.expect("no account_id").clone().into(), + last_known_status: WorkerStatusRecord { + oplog_idx: 0, + status: metadata.status.try_into().expect("invalid status"), + deleted_regions: DeletedRegions::new(), + }, + }), Some(get_worker_metadata_response::Result::Failure(WorkerExecutionError { error: Some(worker_execution_error::Error::WorkerNotFound(_)), })) => None, @@ -499,6 +522,66 @@ impl TestWorkerExecutor { rx } + pub async fn capture_output_forever( + &self, + worker_id: &WorkerId, + ) -> ( + UnboundedReceiver>, + tokio::sync::oneshot::Sender<()>, + ) { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let mut cloned_client = self.client.clone(); + let worker_id = worker_id.clone(); + let (abort_tx, mut abort_rx) = tokio::sync::oneshot::channel(); + tokio::spawn(async move { + let mut abort = false; + while !abort { + let mut response = cloned_client + .connect_worker(ConnectWorkerRequest { + worker_id: Some(worker_id.clone().into()), + account_id: Some( + AccountId { + value: "test-account".to_string(), + } + .into(), + ), + account_limits: None, + }) + .await + .expect("Failed to connect worker") + .into_inner(); + + loop { + select! { + msg = response.message() => { + match msg { + Ok(Some(event)) => { + debug!("Received event: {:?}", event); + tx.send(Some(event)).expect("Failed to send event"); + } + Ok(None) => { + break; + } + Err(e) => { + panic!("Failed to get message: {:?}", e); + } + } + } + _ = (&mut abort_rx) => { + abort = true; + break; + } + } + } + } + + tx.send(None).expect("Failed to send event"); + debug!("Finished receiving events"); + }); + + (rx, abort_tx) + } + pub async fn capture_output_with_termination( &self, worker_id: &WorkerId, @@ -757,6 +840,17 @@ pub fn stdout_event(s: &str) -> LogEvent { } } +pub fn stdout_event_starting_with(event: &LogEvent, s: &str) -> bool { + if let LogEvent { + event: Some(log_event::Event::Stdout(StdOutLog { message })), + } = event + { + message.starts_with(s) + } else { + false + } +} + pub fn stderr_event(s: &str) -> LogEvent { LogEvent { event: Some(log_event::Event::Stderr(StdErrLog { @@ -966,8 +1060,8 @@ impl FuelManagement for TestWorkerCtx { fn borrow_fuel_sync(&mut self) {} - async fn return_fuel(&mut self, current_level: i64) -> Result { - Ok(current_level) + async fn return_fuel(&mut self, _current_level: i64) -> Result { + Ok(0) } } @@ -979,14 +1073,14 @@ impl ExternalOperations for TestWorkerCtx { this: &T, worker_id: &WorkerId, status: WorkerStatus, - ) { + ) -> Result<(), GolemError> { DurableWorkerCtx::::set_worker_status(this, worker_id, status).await } async fn get_worker_retry_count + Send + Sync>( this: &T, worker_id: &WorkerId, - ) -> u32 { + ) -> u64 { DurableWorkerCtx::::get_worker_retry_count(this, worker_id).await } diff --git a/golem-worker-executor-base/tests/lib.rs b/golem-worker-executor-base/tests/lib.rs index 20c1b80812..50d44d1364 100644 --- a/golem-worker-executor-base/tests/lib.rs +++ b/golem-worker-executor-base/tests/lib.rs @@ -20,6 +20,7 @@ pub mod guest_languages; pub mod keyvalue; pub mod rpc; pub mod scalability; +pub mod transactions; pub mod wasi; #[allow(dead_code)] diff --git a/golem-worker-executor-base/tests/transactions.rs b/golem-worker-executor-base/tests/transactions.rs new file mode 100644 index 0000000000..b2858e3f81 --- /dev/null +++ b/golem-worker-executor-base/tests/transactions.rs @@ -0,0 +1,95 @@ +use crate::common; +use assert2::check; +use http_02::{Response, StatusCode}; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use tonic::transport::Body; +use warp::Filter; + +#[tokio::test] +async fn jump() { + let context = common::TestContext::new(); + let mut executor = common::start(&context).await.unwrap(); + + let host_http_port = context.host_http_port(); + + let http_server = tokio::spawn(async move { + let call_count_per_step = Arc::new(Mutex::new(HashMap::::new())); + let route = warp::path("step") + .and(warp::path::param()) + .and(warp::get()) + .map(move |step: u64| { + let mut steps = call_count_per_step.lock().unwrap(); + let step_count = steps.entry(step).and_modify(|e| *e += 1).or_insert(0); + + println!("step: {step} occurrence {step_count}"); + + match &step_count { + 0 => Response::builder() + .status(StatusCode::OK) + .body(Body::from("true")) + .unwrap(), + _ => Response::builder() + .status(StatusCode::OK) + .body(Body::from("false")) + .unwrap(), + } + }); + + warp::serve(route) + .run( + format!("0.0.0.0:{}", host_http_port) + .parse::() + .unwrap(), + ) + .await; + }); + + let template_id = executor.store_template(Path::new("../test-templates/runtime-service.wasm")); + + let mut env = HashMap::new(); + env.insert("PORT".to_string(), context.host_http_port().to_string()); + + let worker_id = executor + .try_start_worker_versioned(&template_id, 0, "runtime-service-jump", vec![], env) + .await + .unwrap(); + + let (rx, abort_capture) = executor.capture_output_forever(&worker_id).await; + + let result = executor + .invoke_and_await(&worker_id, "golem:it/api/jump", vec![]) + .await + .unwrap(); + + drop(executor); + http_server.abort(); + + abort_capture.send(()).unwrap(); + let mut events = common::drain_connection(rx).await; + events.retain(|e| match e { + Some(e) => { + !common::stdout_event_starting_with(e, "Sending") + && !common::stdout_event_starting_with(e, "Received") + } + None => false, + }); + + println!("events: {:?}", events); + + check!(result == vec![common::val_u64(5)]); + check!( + events + == vec![ + Some(common::stdout_event("started: 0\n")), + Some(common::stdout_event("second: 2\n")), + Some(common::stdout_event("second: 2\n")), + Some(common::stdout_event("third: 3\n")), + Some(common::stdout_event("fourth: 4\n")), + Some(common::stdout_event("fourth: 4\n")), + Some(common::stdout_event("fifth: 5\n")), + ] + ); +} diff --git a/golem-worker-executor/src/context.rs b/golem-worker-executor/src/context.rs index fc9a29bfcc..a8aabbe348 100644 --- a/golem-worker-executor/src/context.rs +++ b/golem-worker-executor/src/context.rs @@ -77,8 +77,8 @@ impl FuelManagement for Context { fn borrow_fuel_sync(&mut self) {} - async fn return_fuel(&mut self, current_level: i64) -> Result { - Ok(current_level) + async fn return_fuel(&mut self, _current_level: i64) -> Result { + Ok(0) } } @@ -90,14 +90,14 @@ impl ExternalOperations for Context { this: &T, worker_id: &WorkerId, status: WorkerStatus, - ) { + ) -> Result<(), GolemError> { DurableWorkerCtx::::set_worker_status(this, worker_id, status).await } async fn get_worker_retry_count + Send + Sync>( this: &T, worker_id: &WorkerId, - ) -> u32 { + ) -> u64 { DurableWorkerCtx::::get_worker_retry_count(this, worker_id).await } diff --git a/golem-worker-service-base/src/tokeniser/cursor.rs b/golem-worker-service-base/src/tokeniser/cursor.rs index d9030b8293..8e769e235d 100644 --- a/golem-worker-service-base/src/tokeniser/cursor.rs +++ b/golem-worker-service-base/src/tokeniser/cursor.rs @@ -185,7 +185,6 @@ impl TokenCursor { #[cfg(test)] mod tests { use super::*; - use crate::tokeniser::tokenizer::Tokenizer; #[test] fn capture_string_test() { diff --git a/golem-worker-service/src/service/worker.rs b/golem-worker-service/src/service/worker.rs index de90aeadb8..4b3ad5c1e0 100644 --- a/golem-worker-service/src/service/worker.rs +++ b/golem-worker-service/src/service/worker.rs @@ -174,7 +174,7 @@ pub trait WorkerService { async fn complete_promise( &self, worker_id: &WorkerId, - oplog_id: i32, + oplog_id: u64, data: Vec, ) -> Result; @@ -766,7 +766,7 @@ impl WorkerService for WorkerServiceDefault { async fn complete_promise( &self, worker_id: &WorkerId, - oplog_id: i32, + oplog_id: u64, data: Vec, ) -> Result { let promise_id = PromiseId { @@ -1024,7 +1024,7 @@ impl WorkerService for WorkerServiceNoOp { async fn complete_promise( &self, _worker_id: &WorkerId, - _oplog_id: i32, + _oplog_id: u64, _data: Vec, ) -> Result { Ok(true) diff --git a/openapi/golem-service.yaml b/openapi/golem-service.yaml index a2adb257f3..ac455981dd 100644 --- a/openapi/golem-service.yaml +++ b/openapi/golem-service.yaml @@ -1352,7 +1352,7 @@ components: properties: oplogIdx: type: integer - format: int32 + format: uint64 data: type: array items: @@ -1885,7 +1885,7 @@ components: $ref: '#/components/schemas/WorkerId' oplogIdx: type: integer - format: int32 + format: uint64 required: - workerId - oplogIdx diff --git a/test-templates/http-client/wit/deps/golem/golem-host.wit b/test-templates/http-client/wit/deps/golem/golem-host.wit index a26997abe7..b5c3d4668b 100644 --- a/test-templates/http-client/wit/deps/golem/golem-host.wit +++ b/test-templates/http-client/wit/deps/golem/golem-host.wit @@ -1,46 +1,63 @@ -package golem:api; +package golem:api@0.2.0; +/// The Golem host API provides low level access to Golem specific features such as promises and control over +/// the durability and transactional guarantees the executor provides. interface host { + use golem:rpc/types@0.1.0.{uri}; - record promise-id { - worker-id: worker-id, - oplog-idx: s32, - } + /// An index into the persistent log storing all performed operations of a worker + type oplog-index = u64; - record worker-id { - template-id: template-id, - worker-name: string - } + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + record promise-id { + worker-id: worker-id, + oplog-idx: oplog-index, + } - - record template-id { - uuid: uuid, - } + /// Represents a Golem worker + record worker-id { + template-id: template-id, + worker-name: string + } - record uuid { - high-bits: u64, - low-bits: u64 - } - - record account-id { - account-name: string - } - record uri { - uri: string - } + /// Represents a Golem template + record template-id { + uuid: uuid, + } - golem-create-promise: func() -> promise-id; + /// UUID + record uuid { + high-bits: u64, + low-bits: u64 + } - golem-await-promise: func(promise-id: promise-id) -> list; + /// Create a new promise + golem-create-promise: func() -> promise-id; - golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + golem-await-promise: func(promise-id: promise-id) -> list; - golem-delete-promise: func(promise-id: promise-id) -> (); + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; - get-self-uri: func(function-name: string) -> uri; + /// Deletes the given promise + golem-delete-promise: func(promise-id: promise-id) -> (); + + /// Returns a Golem worker URI that can be used to invoke a given function on the current worker + get-self-uri: func(function-name: string) -> uri; + + /// Returns the current position in the persistent op log + get-oplog-index: func() -> oplog-index; + + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + set-oplog-index: func(oplog-idx: oplog-index) -> (); } world golem-host { - import host; + import host; } \ No newline at end of file diff --git a/test-templates/promise.wasm b/test-templates/promise.wasm index 0eafb8041d..4ecc66786e 100755 Binary files a/test-templates/promise.wasm and b/test-templates/promise.wasm differ diff --git a/test-templates/promise/cargo.toml b/test-templates/promise/cargo.toml index 5852a08d7c..717e4e5eec 100644 --- a/test-templates/promise/cargo.toml +++ b/test-templates/promise/cargo.toml @@ -22,3 +22,4 @@ path = "wit" [package.metadata.component.target.dependencies] "golem:api" = { path = "wit/deps/golem" } +"golem:rpc" = { path = "wit/deps/wasm-rpc" } \ No newline at end of file diff --git a/test-templates/promise/wit/deps/golem/golem-host.wit b/test-templates/promise/wit/deps/golem/golem-host.wit index a26997abe7..b5c3d4668b 100644 --- a/test-templates/promise/wit/deps/golem/golem-host.wit +++ b/test-templates/promise/wit/deps/golem/golem-host.wit @@ -1,46 +1,63 @@ -package golem:api; +package golem:api@0.2.0; +/// The Golem host API provides low level access to Golem specific features such as promises and control over +/// the durability and transactional guarantees the executor provides. interface host { + use golem:rpc/types@0.1.0.{uri}; - record promise-id { - worker-id: worker-id, - oplog-idx: s32, - } + /// An index into the persistent log storing all performed operations of a worker + type oplog-index = u64; - record worker-id { - template-id: template-id, - worker-name: string - } + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + record promise-id { + worker-id: worker-id, + oplog-idx: oplog-index, + } - - record template-id { - uuid: uuid, - } + /// Represents a Golem worker + record worker-id { + template-id: template-id, + worker-name: string + } - record uuid { - high-bits: u64, - low-bits: u64 - } - - record account-id { - account-name: string - } - record uri { - uri: string - } + /// Represents a Golem template + record template-id { + uuid: uuid, + } - golem-create-promise: func() -> promise-id; + /// UUID + record uuid { + high-bits: u64, + low-bits: u64 + } - golem-await-promise: func(promise-id: promise-id) -> list; + /// Create a new promise + golem-create-promise: func() -> promise-id; - golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + golem-await-promise: func(promise-id: promise-id) -> list; - golem-delete-promise: func(promise-id: promise-id) -> (); + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; - get-self-uri: func(function-name: string) -> uri; + /// Deletes the given promise + golem-delete-promise: func(promise-id: promise-id) -> (); + + /// Returns a Golem worker URI that can be used to invoke a given function on the current worker + get-self-uri: func(function-name: string) -> uri; + + /// Returns the current position in the persistent op log + get-oplog-index: func() -> oplog-index; + + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + set-oplog-index: func(oplog-idx: oplog-index) -> (); } world golem-host { - import host; + import host; } \ No newline at end of file diff --git a/test-templates/promise/wit/deps/wasm-rpc/wasm-rpc.wit b/test-templates/promise/wit/deps/wasm-rpc/wasm-rpc.wit new file mode 100644 index 0000000000..f7deff5bf3 --- /dev/null +++ b/test-templates/promise/wit/deps/wasm-rpc/wasm-rpc.wit @@ -0,0 +1,54 @@ +package golem:rpc@0.1.0; + +interface types { + type node-index = s32; + + record wit-value { + nodes: list, + } + + variant wit-node { + record-value(list), + variant-value(tuple>), + enum-value(u32), + flags-value(list), + tuple-value(list), + list-value(list), + option-value(option), + result-value(result, option>), + prim-u8(u8), + prim-u16(u16), + prim-u32(u32), + prim-u64(u64), + prim-s8(s8), + prim-s16(s16), + prim-s32(s32), + prim-s64(s64), + prim-float32(float32), + prim-float64(float64), + prim-char(char), + prim-bool(bool), + prim-string(string), + } + + record uri { + value: string, + } + + variant rpc-error { + protocol-error(string), + denied(string), + not-found(string), + remote-internal-error(string) + } + + resource wasm-rpc { + constructor(location: uri); + + invoke-and-await: func(function-name: string, function-params: list) -> result; + } +} + +world wit-value { + import types; +} diff --git a/test-templates/promise/wit/promise.wit b/test-templates/promise/wit/promise.wit index 047e9a760f..5b4358fff8 100644 --- a/test-templates/promise/wit/promise.wit +++ b/test-templates/promise/wit/promise.wit @@ -1,6 +1,6 @@ package golem:it; world promise { - import golem:api/host; + import golem:api/host@0.2.0; export run: func() -> list; } diff --git a/test-templates/runtime-service.wasm b/test-templates/runtime-service.wasm index 2a033f6b88..593b27ea86 100755 Binary files a/test-templates/runtime-service.wasm and b/test-templates/runtime-service.wasm differ diff --git a/test-templates/runtime-service/Cargo.lock b/test-templates/runtime-service/Cargo.lock index 42302c9794..385d96087f 100644 --- a/test-templates/runtime-service/Cargo.lock +++ b/test-templates/runtime-service/Cargo.lock @@ -2,18 +2,206 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -25,24 +213,352 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fa0ae458eb99874f54c09f4f9174f8b45fb87e854536a4e608696247f0c23" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -74,12 +590,367 @@ dependencies = [ ] [[package]] -name = "runtime-service" -version = "0.0.1" +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "once_cell", - "rand", - "wit-bindgen", + "bitflags 1.3.2", +] + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "git+https://github.com/zivergetech/reqwest?branch=update-feb-2024#77ca01d96ca6ec93d961f8a9f73b631d00267efc" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", + "wit-bindgen", +] + +[[package]] +name = "runtime-service" +version = "0.0.1" +dependencies = [ + "once_cell", + "rand", + "reqwest", + "wit-bindgen", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", ] [[package]] @@ -88,11 +959,340 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-encoder" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d835d67708f6374937c550ad8dd1d17c616ae009e3f00d7a0ac9f7825e78c36a" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.118.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f1154f1ab868e2a01d9834a805faca7bf8b50d041b4ca714d005d0dab1c50c" +dependencies = [ + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b76f1d099678b4f69402a421e888bbe71bf20320c2f3f3565d0e7484dbe5bc20" dependencies = [ - "bitflags", + "bitflags 2.4.0", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d55e1a488af2981fb0edac80d8d20a51ac36897a1bdef4abde33c29c1b6d0d" +dependencies = [ + "anyhow", + "wit-component", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01ff9cae7bf5736750d94d91eb8a49f5e3a04aff1d1a3218287d9b2964510f8" +dependencies = [ + "anyhow", + "heck", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804a98e2538393d47aa7da65a7348116d6ff403b426665152b70a168c0146d49" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", + "wit-component", +] + +[[package]] +name = "wit-component" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2" +dependencies = [ + "anyhow", + "bitflags 2.4.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", ] diff --git a/test-templates/runtime-service/cargo.toml b/test-templates/runtime-service/cargo.toml index ba3b6e1b51..2bb9b3d9cb 100644 --- a/test-templates/runtime-service/cargo.toml +++ b/test-templates/runtime-service/cargo.toml @@ -15,6 +15,7 @@ strip = true [dependencies] once_cell = "1.17.1" rand = "0.8.5" +reqwest = { git = "https://github.com/zivergetech/reqwest", branch = "update-feb-2024", features = ["json"] } wit-bindgen = { version = "0.16.0", default-features = false, features = ["realloc"] } [package.metadata.component.target] diff --git a/test-templates/runtime-service/src/lib.rs b/test-templates/runtime-service/src/lib.rs index 094376e690..6d7336a37b 100644 --- a/test-templates/runtime-service/src/lib.rs +++ b/test-templates/runtime-service/src/lib.rs @@ -1,5 +1,6 @@ mod bindings; +use reqwest::{Client, Response}; use crate::bindings::exports::golem::it::api::Guest; use crate::bindings::golem::api::host::*; @@ -9,4 +10,66 @@ impl Guest for Component { fn get_self_uri(function_name: String) -> String { get_self_uri(&function_name).value } + + fn jump() -> u64 { + let mut state = 0; + + println!("started: {state}"); // 'started 1' + state += 1; + + let state1 = get_oplog_index(); + + state += 1; + println!("second: {state}"); // 'second 2' + + if remote_call(state) { + set_oplog_index(state1); // we resume from state 1, so we emit 'second 2' again but not 'started 1' + } + + state += 1; + println!("third: {state}"); // 'third 3' + + let state2 = get_oplog_index(); + + state += 1; + println!("fourth: {state}"); // 'fourth 4' + + if remote_call(state) { + set_oplog_index(state2); // we resume from state 2, so emit 'fourth 4' again but not the rest + } + + state += 1; + println!("fifth: {state}"); // 'fifth 5' + + // Expected final output: + // started 1 + // second 2 + // second 2 + // third 3 + // fourth 4 + // fourth 4 + // fifth 5 + + state // final value is 5 + } } + +fn remote_call(param: u64) -> bool { + let port = std::env::var("PORT").unwrap_or("9999".to_string()); + + let client = Client::builder().build().unwrap(); + + let url = format!("http://localhost:{port}/step/{param}"); + + println!("Sending GET {url}"); + + let response: Response = client.get(&url) + .send() + .expect("Request failed"); + + let status = response.status(); + let body = response.json::().expect("Invalid response"); + + println!("Received {status} {body}"); + body +} \ No newline at end of file diff --git a/test-templates/runtime-service/wit/deps/golem/golem-host.wit b/test-templates/runtime-service/wit/deps/golem/golem-host.wit index bf70bcebcc..b5c3d4668b 100644 --- a/test-templates/runtime-service/wit/deps/golem/golem-host.wit +++ b/test-templates/runtime-service/wit/deps/golem/golem-host.wit @@ -1,43 +1,63 @@ -package golem:api; +package golem:api@0.2.0; +/// The Golem host API provides low level access to Golem specific features such as promises and control over +/// the durability and transactional guarantees the executor provides. interface host { - use golem:rpc/types@0.1.0.{uri}; + use golem:rpc/types@0.1.0.{uri}; - record promise-id { - worker-id: worker-id, - oplog-idx: s32, - } + /// An index into the persistent log storing all performed operations of a worker + type oplog-index = u64; - record worker-id { - template-id: template-id, - worker-name: string - } + /// A promise ID is a value that can be passed to an external Golem API to complete that promise + /// from an arbitrary external source, while Golem workers can await for this completion. + record promise-id { + worker-id: worker-id, + oplog-idx: oplog-index, + } - - record template-id { - uuid: uuid, - } + /// Represents a Golem worker + record worker-id { + template-id: template-id, + worker-name: string + } - record uuid { - high-bits: u64, - low-bits: u64 - } - - record account-id { - account-name: string - } - golem-create-promise: func() -> promise-id; + /// Represents a Golem template + record template-id { + uuid: uuid, + } - golem-await-promise: func(promise-id: promise-id) -> list; + /// UUID + record uuid { + high-bits: u64, + low-bits: u64 + } - golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; + /// Create a new promise + golem-create-promise: func() -> promise-id; - golem-delete-promise: func(promise-id: promise-id) -> (); + /// Suspends execution until the given promise gets completed, and returns the payload passed to + /// the promise completion. + golem-await-promise: func(promise-id: promise-id) -> list; - get-self-uri: func(function-name: string) -> uri; + /// Completes the given promise with the given payload. Returns true if the promise was completed, false + /// if the promise was already completed. The payload is passed to the worker that is awaiting the promise. + golem-complete-promise: func(promise-id: promise-id, data: list) -> bool; + + /// Deletes the given promise + golem-delete-promise: func(promise-id: promise-id) -> (); + + /// Returns a Golem worker URI that can be used to invoke a given function on the current worker + get-self-uri: func(function-name: string) -> uri; + + /// Returns the current position in the persistent op log + get-oplog-index: func() -> oplog-index; + + /// Makes the current worker travel back in time and continue execution from the given position in the persistent + /// op log. + set-oplog-index: func(oplog-idx: oplog-index) -> (); } world golem-host { - import host; + import host; } \ No newline at end of file diff --git a/test-templates/runtime-service/wit/runtime.wit b/test-templates/runtime-service/wit/runtime.wit index 7ab5078b55..ba5af09e8a 100644 --- a/test-templates/runtime-service/wit/runtime.wit +++ b/test-templates/runtime-service/wit/runtime.wit @@ -2,9 +2,10 @@ package golem:it; interface api { get-self-uri: func(function-name: string) -> string; + jump: func() -> u64; } world runtime-service { - import golem:api/host; + import golem:api/host@0.2.0; export api; } \ No newline at end of file