chalk-remote-call-python 1.8.1__tar.gz → 1.8.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {chalk_remote_call_python-1.8.1/chalk_remote_call_python.egg-info → chalk_remote_call_python-1.8.3}/PKG-INFO +1 -1
  2. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/Cargo.lock +32 -19
  3. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/Cargo.toml +4 -4
  4. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/async_service.rs +21 -3
  5. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/coalesce.rs +65 -4
  6. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/lib.rs +34 -17
  7. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/metrics.rs +6 -4
  8. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/python_bridge.rs +33 -9
  9. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/self_consumer.rs +81 -22
  10. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/server.rs +24 -8
  11. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-server/src/service.rs +42 -3
  12. chalk_remote_call_python-1.8.3/chalk_remote_call/_version.py +1 -0
  13. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3/chalk_remote_call_python.egg-info}/PKG-INFO +1 -1
  14. chalk_remote_call_python-1.8.1/chalk_remote_call/_version.py +0 -1
  15. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/MANIFEST.in +0 -0
  16. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/README.md +0 -0
  17. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/Cargo.toml +0 -0
  18. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/Cargo.toml +0 -0
  19. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.auth.v1.rs +0 -0
  20. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.common.v1.rs +0 -0
  21. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.rs +0 -0
  22. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.tonic.rs +0 -0
  23. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.utils.v1.rs +0 -0
  24. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/descriptor.bin +0 -0
  25. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/chalk-remote-call-proto/src/lib.rs +0 -0
  26. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk-remote-call-rs/rust-toolchain.toml +0 -0
  27. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/__init__.py +0 -0
  28. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/__main__.py +0 -0
  29. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/__init__.py +0 -0
  30. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/__init__.py +0 -0
  31. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/auth/__init__.py +0 -0
  32. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/auth/v1/__init__.py +0 -0
  33. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2.py +0 -0
  34. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2_grpc.py +0 -0
  35. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/common/__init__.py +0 -0
  36. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/common/v1/__init__.py +0 -0
  37. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2.py +0 -0
  38. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2_grpc.py +0 -0
  39. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
  40. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
  41. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +0 -0
  42. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +0 -0
  43. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/__init__.py +0 -0
  44. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/__init__.py +0 -0
  45. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2.py +0 -0
  46. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2_grpc.py +0 -0
  47. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2.py +0 -0
  48. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2_grpc.py +0 -0
  49. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2.py +0 -0
  50. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2_grpc.py +0 -0
  51. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/_native.pyi +0 -0
  52. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/arrow_utils.py +0 -0
  53. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/cli.py +0 -0
  54. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/handler_loader.py +0 -0
  55. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/input_transform.py +0 -0
  56. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/server.py +0 -0
  57. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/servicer.py +0 -0
  58. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call/tracing.py +0 -0
  59. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call_python.egg-info/SOURCES.txt +0 -0
  60. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call_python.egg-info/dependency_links.txt +0 -0
  61. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call_python.egg-info/entry_points.txt +0 -0
  62. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call_python.egg-info/requires.txt +0 -0
  63. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/chalk_remote_call_python.egg-info/top_level.txt +0 -0
  64. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/pyproject.toml +0 -0
  65. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/setup.cfg +0 -0
  66. {chalk_remote_call_python-1.8.1 → chalk_remote_call_python-1.8.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 1.8.1
3
+ Version: 1.8.3
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -483,8 +483,8 @@ dependencies = [
483
483
 
484
484
  [[package]]
485
485
  name = "chalk-core"
486
- version = "0.1.0"
487
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
486
+ version = "0.0.0"
487
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
488
488
  dependencies = [
489
489
  "anyhow",
490
490
  "envconfig",
@@ -498,8 +498,8 @@ dependencies = [
498
498
 
499
499
  [[package]]
500
500
  name = "chalk-protos"
501
- version = "0.1.0"
502
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
501
+ version = "0.0.0"
502
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
503
503
  dependencies = [
504
504
  "prost 0.11.9",
505
505
  "prost-build",
@@ -510,8 +510,8 @@ dependencies = [
510
510
 
511
511
  [[package]]
512
512
  name = "chalk-queue"
513
- version = "0.1.0"
514
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
513
+ version = "0.0.0"
514
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
515
515
  dependencies = [
516
516
  "base64 0.22.1",
517
517
  "redis",
@@ -559,8 +559,8 @@ dependencies = [
559
559
 
560
560
  [[package]]
561
561
  name = "chalk_metric_definitions"
562
- version = "0.1.0"
563
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
562
+ version = "0.0.0"
563
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
564
564
  dependencies = [
565
565
  "chalk-protos",
566
566
  "metrics-generator",
@@ -569,8 +569,8 @@ dependencies = [
569
569
 
570
570
  [[package]]
571
571
  name = "chalk_metrics"
572
- version = "0.1.0"
573
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
572
+ version = "0.0.0"
573
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
574
574
  dependencies = [
575
575
  "async-trait",
576
576
  "chalk-core",
@@ -603,8 +603,8 @@ dependencies = [
603
603
 
604
604
  [[package]]
605
605
  name = "chalk_statsd"
606
- version = "0.1.0"
607
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
606
+ version = "0.0.0"
607
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
608
608
  dependencies = [
609
609
  "chalk-core",
610
610
  "chrono",
@@ -874,7 +874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
874
874
  checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
875
875
  dependencies = [
876
876
  "libc",
877
- "windows-sys 0.52.0",
877
+ "windows-sys 0.61.2",
878
878
  ]
879
879
 
880
880
  [[package]]
@@ -1472,7 +1472,7 @@ dependencies = [
1472
1472
  "libc",
1473
1473
  "percent-encoding",
1474
1474
  "pin-project-lite",
1475
- "socket2 0.5.10",
1475
+ "socket2 0.6.3",
1476
1476
  "tokio",
1477
1477
  "tower-service",
1478
1478
  "tracing",
@@ -1867,7 +1867,7 @@ dependencies = [
1867
1867
  [[package]]
1868
1868
  name = "metrics-generator"
1869
1869
  version = "0.1.0"
1870
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=44c26fd738fabd7e2223854618d1f9ccb1b08a71#44c26fd738fabd7e2223854618d1f9ccb1b08a71"
1870
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=ae8e792f746f77253f682ee0c56b246cfdd88f51#ae8e792f746f77253f682ee0c56b246cfdd88f51"
1871
1871
  dependencies = [
1872
1872
  "codegen",
1873
1873
  "serde",
@@ -2100,7 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2100
2100
  checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
2101
2101
  dependencies = [
2102
2102
  "libc",
2103
- "windows-sys 0.52.0",
2103
+ "windows-sys 0.61.2",
2104
2104
  ]
2105
2105
 
2106
2106
  [[package]]
@@ -2761,7 +2761,7 @@ dependencies = [
2761
2761
  "errno",
2762
2762
  "libc",
2763
2763
  "linux-raw-sys 0.12.1",
2764
- "windows-sys 0.52.0",
2764
+ "windows-sys 0.61.2",
2765
2765
  ]
2766
2766
 
2767
2767
  [[package]]
@@ -3128,10 +3128,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3128
3128
  checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
3129
3129
  dependencies = [
3130
3130
  "fastrand",
3131
- "getrandom 0.3.4",
3131
+ "getrandom 0.4.2",
3132
3132
  "once_cell",
3133
3133
  "rustix 1.1.4",
3134
- "windows-sys 0.52.0",
3134
+ "windows-sys 0.61.2",
3135
3135
  ]
3136
3136
 
3137
3137
  [[package]]
@@ -3607,6 +3607,16 @@ dependencies = [
3607
3607
  "tracing-core",
3608
3608
  ]
3609
3609
 
3610
+ [[package]]
3611
+ name = "tracing-serde"
3612
+ version = "0.2.0"
3613
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3614
+ checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
3615
+ dependencies = [
3616
+ "serde",
3617
+ "tracing-core",
3618
+ ]
3619
+
3610
3620
  [[package]]
3611
3621
  name = "tracing-subscriber"
3612
3622
  version = "0.3.23"
@@ -3617,12 +3627,15 @@ dependencies = [
3617
3627
  "nu-ansi-term",
3618
3628
  "once_cell",
3619
3629
  "regex-automata",
3630
+ "serde",
3631
+ "serde_json",
3620
3632
  "sharded-slab",
3621
3633
  "smallvec",
3622
3634
  "thread_local",
3623
3635
  "tracing",
3624
3636
  "tracing-core",
3625
3637
  "tracing-log",
3638
+ "tracing-serde",
3626
3639
  ]
3627
3640
 
3628
3641
  [[package]]
@@ -19,7 +19,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "signa
19
19
  tokio-stream = "0.1"
20
20
  arrow = { version = "54", default-features = false, features = ["ipc"] }
21
21
  tracing = "0.1"
22
- tracing-subscriber = { version = "0.3", features = ["env-filter"] }
22
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
23
23
  bytes = "1"
24
24
  uuid = { version = "1", features = ["v4"] }
25
25
  dashmap = "5"
@@ -36,6 +36,6 @@ futures-util = "0.3"
36
36
  # `[patch.crates-io]` here it builds against vanilla crates.io redis (it only
37
37
  # uses standard APIs). Same rev across all three so cargo reuses one checkout.
38
38
  [target.'cfg(target_os = "linux")'.dependencies]
39
- chalk-queue = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "44c26fd738fabd7e2223854618d1f9ccb1b08a71" }
40
- chalk_metrics = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "44c26fd738fabd7e2223854618d1f9ccb1b08a71" }
41
- chalk_statsd = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "44c26fd738fabd7e2223854618d1f9ccb1b08a71" }
39
+ chalk-queue = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "ae8e792f746f77253f682ee0c56b246cfdd88f51" }
40
+ chalk_metrics = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "ae8e792f746f77253f682ee0c56b246cfdd88f51" }
41
+ chalk_statsd = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "ae8e792f746f77253f682ee0c56b246cfdd88f51" }
@@ -10,7 +10,7 @@ use chalk_remote_call_proto::chalk::runtime::v1::{
10
10
  PollRemoteCallResponse, PurgeQueueRequest, PurgeQueueResponse, RemoteCallStatus,
11
11
  };
12
12
  use tonic::{Request, Response, Status};
13
- use tracing::{debug, error, info, instrument};
13
+ use tracing::{debug, error, info, instrument, warn};
14
14
 
15
15
  use crate::python_bridge::{ChunkBuffer, PythonHandler};
16
16
  use crate::service::extract_metadata;
@@ -87,10 +87,15 @@ pub struct AsyncRemoteCallServiceImpl {
87
87
  /// the per-call dispatch does not satisfy, so async enqueue is refused
88
88
  /// rather than silently calling the handler with the wrong shape.
89
89
  batching_enabled: bool,
90
+ is_remote_function: bool,
90
91
  }
91
92
 
92
93
  impl AsyncRemoteCallServiceImpl {
93
- pub fn new(python_handler: Arc<PythonHandler>, batching_enabled: bool) -> Self {
94
+ pub fn new(
95
+ python_handler: Arc<PythonHandler>,
96
+ batching_enabled: bool,
97
+ is_remote_function: bool,
98
+ ) -> Self {
94
99
  let registry: Registry = Arc::new(Mutex::new(HashMap::new()));
95
100
  // Runs inside the server's tokio runtime (the service is constructed
96
101
  // within `run_server`); reclaims orphaned terminal entries.
@@ -100,6 +105,7 @@ impl AsyncRemoteCallServiceImpl {
100
105
  registry,
101
106
  next_id: AtomicU64::new(1),
102
107
  batching_enabled,
108
+ is_remote_function,
103
109
  }
104
110
  }
105
111
  }
@@ -125,6 +131,13 @@ impl AsyncRemoteCallService for AsyncRemoteCallServiceImpl {
125
131
  let req = request.into_inner();
126
132
  let function_name = req.name;
127
133
 
134
+ if self.is_remote_function {
135
+ warn!(
136
+ function_name = %function_name,
137
+ "this enqueue is not fronted by the fnq server (CHALK_REMOTE_FUNCTION is set to true)"
138
+ );
139
+ }
140
+
128
141
  // Only inline feather bytes are supported today; the object-store
129
142
  // variant is reserved for large payloads and not wired up here.
130
143
  let ipc_bytes = match req.args.and_then(|a| a.args) {
@@ -177,7 +190,12 @@ impl AsyncRemoteCallService for AsyncRemoteCallServiceImpl {
177
190
  Err(e) => {
178
191
  s.status = RemoteCallStatus::Failed;
179
192
  s.error = Some(e.to_string());
180
- error!(call_id = %id, error = %e, "async call failed");
193
+ error!(
194
+ call_id = %id,
195
+ exception.stacktrace = e.details(),
196
+ "async call failed: {}",
197
+ e.details()
198
+ );
181
199
  }
182
200
  }
183
201
  }
@@ -13,6 +13,8 @@ use tokio::task::JoinHandle;
13
13
  use tonic::Status;
14
14
  use tracing::error;
15
15
 
16
+ #[cfg(target_os = "linux")]
17
+ use chalk_metrics::PublishingMetricsPipeline;
16
18
  use chalk_remote_call_proto::chalk::runtime::v1::CallFunctionResponse;
17
19
 
18
20
  use crate::python_bridge::{CallerInput, PythonHandler};
@@ -35,10 +37,17 @@ pub struct CoalescingQueue {
35
37
  max_size: usize,
36
38
  max_duration: Duration,
37
39
  state: Mutex<QueueState>,
40
+ #[cfg(target_os = "linux")]
41
+ pipeline: Option<Arc<PublishingMetricsPipeline>>,
38
42
  }
39
43
 
40
44
  impl CoalescingQueue {
41
- pub fn new(handler: Arc<PythonHandler>, max_size: usize, max_duration_ms: u64) -> Arc<Self> {
45
+ pub fn new(
46
+ handler: Arc<PythonHandler>,
47
+ max_size: usize,
48
+ max_duration_ms: u64,
49
+ #[cfg(target_os = "linux")] pipeline: Option<Arc<PublishingMetricsPipeline>>,
50
+ ) -> Arc<Self> {
42
51
  Arc::new(Self {
43
52
  handler,
44
53
  max_size,
@@ -47,6 +56,8 @@ impl CoalescingQueue {
47
56
  items: Vec::new(),
48
57
  flush_task: None,
49
58
  }),
59
+ #[cfg(target_os = "linux")]
60
+ pipeline,
50
61
  })
51
62
  }
52
63
 
@@ -90,17 +101,33 @@ impl CoalescingQueue {
90
101
  // Dispatch off the queue's critical section — don't block further
91
102
  // submissions while Python runs.
92
103
  let handler = Arc::clone(&self.handler);
104
+ #[cfg(target_os = "linux")]
105
+ let pipeline = self.pipeline.clone();
93
106
  tokio::spawn(async move {
94
- Self::dispatch(handler, items).await;
107
+ Self::dispatch(
108
+ handler,
109
+ items,
110
+ #[cfg(target_os = "linux")]
111
+ pipeline,
112
+ )
113
+ .await;
95
114
  });
96
115
  }
97
116
 
98
- async fn dispatch(handler: Arc<PythonHandler>, items: Vec<BufferedCall>) {
117
+ async fn dispatch(
118
+ handler: Arc<PythonHandler>,
119
+ items: Vec<BufferedCall>,
120
+ #[cfg(target_os = "linux")] pipeline: Option<Arc<PublishingMetricsPipeline>>,
121
+ ) {
99
122
  // Split each BufferedCall into its input (for the bridge) and its
100
123
  // response channel (for routing the reply back).
101
124
  let n = items.len();
102
125
  let mut callers: Vec<CallerInput> = Vec::with_capacity(n);
103
126
  let mut response_txs = Vec::with_capacity(n);
127
+ #[cfg(target_os = "linux")]
128
+ let function_name = items[0].input.function_name.clone();
129
+ #[cfg(target_os = "linux")]
130
+ let started = std::time::Instant::now();
104
131
  for item in items {
105
132
  callers.push(item.input);
106
133
  response_txs.push(item.response_tx);
@@ -116,11 +143,31 @@ impl CoalescingQueue {
116
143
  n
117
144
  );
118
145
  error!("{}", msg);
146
+ #[cfg(target_os = "linux")]
147
+ if let Some(p) = &pipeline {
148
+ crate::metrics::record_completed_call(
149
+ p,
150
+ &function_name,
151
+ /* success = */ false,
152
+ started.elapsed(),
153
+ chalk_metrics::metrics::tags::Mode::Sync,
154
+ );
155
+ }
119
156
  for tx in &response_txs {
120
157
  let _ = tx.send(Err(Status::unknown(msg.clone()))).await;
121
158
  }
122
159
  return;
123
160
  }
161
+ #[cfg(target_os = "linux")]
162
+ if let Some(p) = &pipeline {
163
+ crate::metrics::record_completed_call(
164
+ p,
165
+ &function_name,
166
+ /* success = */ true,
167
+ started.elapsed(),
168
+ chalk_metrics::metrics::tags::Mode::Sync,
169
+ );
170
+ }
124
171
  for (tx, chunk) in response_txs.into_iter().zip(per_caller_bytes) {
125
172
  let msg = CallFunctionResponse {
126
173
  feather_stream: chunk.into(),
@@ -129,9 +176,23 @@ impl CoalescingQueue {
129
176
  }
130
177
  }
131
178
  Err(e) => {
132
- error!("Coalesced handler error: {}", e);
179
+ error!(
180
+ exception.stacktrace = e.details(),
181
+ "Coalesced handler error: {}",
182
+ e.details()
183
+ );
133
184
  // IPC decode errors are already caught by the service's
134
185
  // pre-validation; anything reaching here is a handler error.
186
+ #[cfg(target_os = "linux")]
187
+ if let Some(p) = &pipeline {
188
+ crate::metrics::record_completed_call(
189
+ p,
190
+ &function_name,
191
+ /* success = */ false,
192
+ started.elapsed(),
193
+ chalk_metrics::metrics::tags::Mode::Sync,
194
+ );
195
+ }
135
196
  let status =
136
197
  Status::unknown(format!("Exception raised during handler execution: {e}"));
137
198
  for tx in response_txs {
@@ -44,8 +44,12 @@ fn start_server(
44
44
  max_batching_size: usize,
45
45
  max_buffer_duration_ms: u64,
46
46
  ) -> PyResult<()> {
47
- // Initialize tracing (respects RUST_LOG env var, defaults to info)
47
+ // Kubernetes consumes stdout/stderr as a machine-readable log stream. Keep
48
+ // event fields flattened so collectors can read `level`, `target`,
49
+ // `message`, and fields such as `error` without parsing terminal output.
48
50
  let _ = tracing_subscriber::fmt()
51
+ .json()
52
+ .flatten_event(true)
49
53
  .with_env_filter(
50
54
  EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
51
55
  )
@@ -53,16 +57,6 @@ fn start_server(
53
57
 
54
58
  let python_handler = Arc::new(PythonHandler::new(handler, process_fn, arg_names));
55
59
 
56
- let coalescing_queue = if max_batching_size > 0 {
57
- Some(CoalescingQueue::new(
58
- Arc::clone(&python_handler),
59
- max_batching_size,
60
- max_buffer_duration_ms,
61
- ))
62
- } else {
63
- None
64
- };
65
-
66
60
  // Release the GIL while the Rust server runs
67
61
  py.detach(|| {
68
62
  let rt = tokio::runtime::Builder::new_multi_thread()
@@ -75,12 +69,35 @@ fn start_server(
75
69
  ))
76
70
  })?;
77
71
 
78
- rt.block_on(server::run_server(
79
- host,
80
- port,
81
- python_handler,
82
- coalescing_queue,
83
- ))
72
+ rt.block_on(async {
73
+ // Linux-only: the self-consumer + its metrics pipeline depend on
74
+ // chalk-queue / chalk_metrics, since these don't compile on macos
75
+ // and windows. Built inside the runtime since it needs to `.await`.
76
+ #[cfg(target_os = "linux")]
77
+ let pipeline = crate::metrics::init_pipeline().await;
78
+
79
+ let coalescing_queue = if max_batching_size > 0 {
80
+ Some(CoalescingQueue::new(
81
+ Arc::clone(&python_handler),
82
+ max_batching_size,
83
+ max_buffer_duration_ms,
84
+ #[cfg(target_os = "linux")]
85
+ pipeline.clone(),
86
+ ))
87
+ } else {
88
+ None
89
+ };
90
+
91
+ server::run_server(
92
+ host,
93
+ port,
94
+ python_handler,
95
+ coalescing_queue,
96
+ #[cfg(target_os = "linux")]
97
+ pipeline,
98
+ )
99
+ .await
100
+ })
84
101
  .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Server error: {e}")))
85
102
  })
86
103
  }
@@ -10,7 +10,7 @@
10
10
  use std::sync::Arc;
11
11
  use std::time::Duration;
12
12
 
13
- use chalk_metrics::metrics::tags::{FunctionName, Status as MetricStatus, TagValue};
13
+ use chalk_metrics::metrics::tags::{FunctionName, Mode, Status as MetricStatus, TagValue};
14
14
  use chalk_metrics::metrics::well_known_metrics::WellKnownMetricName;
15
15
  use chalk_metrics::{
16
16
  make_pubsub_publisher, BatchConfig, BusPublisher, MetricsPipeline, PublishingMetricsPipeline,
@@ -30,7 +30,7 @@ const BATCH_MAX_LATENCY: Duration = Duration::from_millis(100);
30
30
  /// The bus publisher is what reaches the metrics store (and thus the dashboard);
31
31
  /// statsd alone only reaches Datadog. An empty `METRICS_BUS_TOPIC` therefore
32
32
  /// means the dashboard won't move — surfaced as a warning, not an error.
33
- pub async fn init_pipeline() -> Option<Arc<dyn MetricsPipeline>> {
33
+ pub async fn init_pipeline() -> Option<Arc<PublishingMetricsPipeline>> {
34
34
  let environment_id = std::env::var("CHALK_ENVIRONMENT_ID").unwrap_or_default();
35
35
 
36
36
  let statsd = Arc::new(
@@ -45,7 +45,7 @@ pub async fn init_pipeline() -> Option<Arc<dyn MetricsPipeline>> {
45
45
  warn!("no metrics bus publisher configured; function call metrics will not reach the dashboard");
46
46
  }
47
47
 
48
- let metrics_pipeline: Arc<dyn MetricsPipeline> =
48
+ let metrics_pipeline: Arc<PublishingMetricsPipeline> =
49
49
  Arc::new(PublishingMetricsPipeline::new_with_env_var_defaults(
50
50
  environment_id,
51
51
  String::new(),
@@ -111,10 +111,11 @@ async fn build_bus_publisher() -> Option<Arc<dyn BusPublisher>> {
111
111
  /// carry the scaling-group revision tag used by the legacy catalog consumer;
112
112
  /// the dashboard filters by function name.
113
113
  pub fn record_completed_call(
114
- pipeline: &Arc<dyn MetricsPipeline>,
114
+ pipeline: &Arc<PublishingMetricsPipeline>,
115
115
  function_name: &str,
116
116
  success: bool,
117
117
  elapsed: Duration,
118
+ mode: Mode,
118
119
  ) {
119
120
  let status = if success {
120
121
  MetricStatus::Success
@@ -124,6 +125,7 @@ pub fn record_completed_call(
124
125
  let tags = vec![
125
126
  TagValue::FunctionName(FunctionName(function_name.to_string())),
126
127
  TagValue::Status(status.clone()),
128
+ TagValue::Mode(mode.clone()),
127
129
  ];
128
130
 
129
131
  if let Err(e) = pipeline.count(WellKnownMetricName::FunctionCallDequeued, 1.0, tags.clone()) {
@@ -74,7 +74,7 @@ impl PythonHandler {
74
74
  })
75
75
  })
76
76
  .await
77
- .map_err(|e| PythonError(format!("Task join error: {e}")))?
77
+ .map_err(|e| PythonError::new(format!("Task join error: {e}")))?
78
78
  }
79
79
 
80
80
  /// Non-batched streaming dispatch. Calls `process_batches(..., emit)` so
@@ -147,7 +147,7 @@ impl PythonHandler {
147
147
  })
148
148
  })
149
149
  .await
150
- .map_err(|e| PythonError(format!("Task join error: {e}")))?
150
+ .map_err(|e| PythonError::new(format!("Task join error: {e}")))?
151
151
  }
152
152
  }
153
153
 
@@ -305,13 +305,13 @@ fn invoke_process_fn(
305
305
  let result_list: &Bound<'_, PyList> = result
306
306
  .bind(py)
307
307
  .cast::<PyList>()
308
- .map_err(|e| PythonError(format!("bridge must return a list, got: {e}")))?;
308
+ .map_err(|e| PythonError::new(format!("bridge must return a list, got: {e}")))?;
309
309
 
310
310
  let mut out = Vec::with_capacity(result_list.len());
311
311
  for item in result_list.iter() {
312
- let bytes_ref: &Bound<'_, PyBytes> = item
313
- .cast::<PyBytes>()
314
- .map_err(|e| PythonError(format!("bridge must return list[bytes], got item: {e}")))?;
312
+ let bytes_ref: &Bound<'_, PyBytes> = item.cast::<PyBytes>().map_err(|e| {
313
+ PythonError::new(format!("bridge must return list[bytes], got item: {e}"))
314
+ })?;
315
315
  out.push(bytes_ref.as_bytes().to_vec());
316
316
  }
317
317
  Ok(out)
@@ -319,14 +319,38 @@ fn invoke_process_fn(
319
319
 
320
320
  /// Error type for Python handler invocations.
321
321
  #[derive(Debug)]
322
- pub struct PythonError(pub String);
322
+ pub struct PythonError {
323
+ details: String,
324
+ }
325
+
326
+ impl PythonError {
327
+ fn new(message: String) -> Self {
328
+ Self { details: message }
329
+ }
330
+
331
+ pub fn details(&self) -> &str {
332
+ &self.details
333
+ }
334
+ }
323
335
 
324
336
  impl std::fmt::Display for PythonError {
325
337
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326
- write!(f, "{}", self.0)
338
+ write!(f, "{}", self.details)
327
339
  }
328
340
  }
329
341
 
330
342
  fn into_python_error(py: Python<'_>, err: PyErr) -> PythonError {
331
- PythonError(format!("{}", err.value(py)))
343
+ let summary = err.to_string();
344
+ let details = format_python_traceback(py, &err).unwrap_or_else(|_| summary.clone());
345
+ PythonError { details }
346
+ }
347
+
348
+ fn format_python_traceback(py: Python<'_>, err: &PyErr) -> PyResult<String> {
349
+ let traceback = py.import("traceback")?;
350
+ let formatted = traceback.call_method1(
351
+ "format_exception",
352
+ (err.get_type(py), err.value(py), err.traceback(py)),
353
+ )?;
354
+ let lines: Vec<String> = formatted.extract()?;
355
+ Ok(lines.concat().trim_end().to_string())
332
356
  }
@@ -28,6 +28,7 @@
28
28
  //! caller. The semaphore still admits one slot per call, so it remains the
29
29
  //! producer of the concurrency the coalescer batches.
30
30
 
31
+ use std::collections::HashMap;
31
32
  use std::panic::AssertUnwindSafe;
32
33
  use std::sync::{Arc, Mutex};
33
34
  use std::time::{Duration, Instant};
@@ -44,7 +45,8 @@ use tokio::sync::mpsc;
44
45
  use tracing::{error, info, warn};
45
46
  use uuid::Uuid;
46
47
 
47
- use chalk_metrics::MetricsPipeline;
48
+ use chalk_metrics::metrics::tags::Mode;
49
+ use chalk_metrics::PublishingMetricsPipeline;
48
50
 
49
51
  use crate::coalesce::{BufferedCall, CoalescingQueue};
50
52
  use crate::python_bridge::{CallerInput, ChunkBuffer, PythonHandler};
@@ -186,7 +188,7 @@ type LiveTokens = Arc<DashSet<String>>;
186
188
  pub async fn run(
187
189
  config: SelfConsumerConfig,
188
190
  handler: Arc<PythonHandler>,
189
- metrics_pipeline: Option<Arc<dyn MetricsPipeline>>,
191
+ metrics_pipeline: Option<Arc<PublishingMetricsPipeline>>,
190
192
  coalescing_queue: Option<Arc<CoalescingQueue>>,
191
193
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
192
194
  let client = Client::open(config.redis_url.clone())?;
@@ -310,7 +312,7 @@ async fn run_one_call(
310
312
  lease: SemaphoreLease,
311
313
  live_tokens: LiveTokens,
312
314
  flush_timeout: Duration,
313
- metrics_pipeline: Option<Arc<dyn MetricsPipeline>>,
315
+ metrics_pipeline: Option<Arc<PublishingMetricsPipeline>>,
314
316
  ) {
315
317
  let call_id = work.call_id.clone();
316
318
  match AssertUnwindSafe(execute_and_write(
@@ -362,7 +364,7 @@ async fn run_one_coalesced_call(
362
364
  work: QueuedCall,
363
365
  lease: SemaphoreLease,
364
366
  live_tokens: LiveTokens,
365
- metrics_pipeline: Option<Arc<dyn MetricsPipeline>>,
367
+ metrics_pipeline: Option<Arc<PublishingMetricsPipeline>>,
366
368
  ) {
367
369
  let call_id = work.call_id.clone();
368
370
  if let Err(e) = coalesced_execute_and_write(
@@ -397,13 +399,14 @@ async fn coalesced_execute_and_write(
397
399
  queue: &FunctionQueue,
398
400
  conn: &mut ConnectionManager,
399
401
  work: QueuedCall,
400
- metrics_pipeline: Option<&Arc<dyn MetricsPipeline>>,
402
+ metrics_pipeline: Option<&Arc<PublishingMetricsPipeline>>,
401
403
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
402
404
  let call_uuid = Uuid::parse_str(&work.call_id)?;
403
405
  let stream_key = queue.stream_key_for(call_uuid);
404
406
  let ipc_bytes = work.call.feather_bytes()?;
405
407
  let function_name = work.call.name.clone();
406
- let metadata = work.metadata.otel_headers.clone();
408
+ let mut metadata = work.metadata.otel_headers.clone();
409
+ let processing_trace_id = ensure_traceparent(&mut metadata);
407
410
  let started = Instant::now();
408
411
 
409
412
  let _ = queue.update_status(call_uuid, "running").await;
@@ -430,7 +433,13 @@ async fn coalesced_execute_and_write(
430
433
  // stream writes below — parity with the streaming path.
431
434
  let success = matches!(response, Some(Ok(_)));
432
435
  if let Some(p) = metrics_pipeline {
433
- crate::metrics::record_completed_call(p, &function_name, success, started.elapsed());
436
+ crate::metrics::record_completed_call(
437
+ p,
438
+ &function_name,
439
+ success,
440
+ started.elapsed(),
441
+ Mode::Async,
442
+ );
434
443
  }
435
444
 
436
445
  match response {
@@ -438,7 +447,12 @@ async fn coalesced_execute_and_write(
438
447
  stream_writer::write_chunk(conn, &stream_key, 1, &resp.feather_stream).await?;
439
448
  stream_writer::write_end(conn, &stream_key, 2).await?;
440
449
  let _ = queue
441
- .complete_status(call_uuid, "completed", "1 chunk(s)")
450
+ .complete_status(
451
+ call_uuid,
452
+ "completed",
453
+ "1 chunk(s)",
454
+ Some(processing_trace_id),
455
+ )
442
456
  .await;
443
457
  info!(call_id = %work.call_id, "coalesced queued call completed");
444
458
  }
@@ -455,7 +469,9 @@ async fn coalesced_execute_and_write(
455
469
  )
456
470
  .await?;
457
471
  let summary: String = status.message().chars().take(200).collect();
458
- let _ = queue.complete_status(call_uuid, "failed", &summary).await;
472
+ let _ = queue
473
+ .complete_status(call_uuid, "failed", &summary, Some(processing_trace_id))
474
+ .await;
459
475
  error!(call_id = %work.call_id, error = %status, "coalesced queued call failed");
460
476
  }
461
477
  None => {
@@ -465,13 +481,31 @@ async fn coalesced_execute_and_write(
465
481
  let msg = "coalesced dispatch produced no response";
466
482
  stream_writer::write_error(conn, &stream_key, 1, "RemoteFunctionError", msg, None)
467
483
  .await?;
468
- let _ = queue.complete_status(call_uuid, "failed", msg).await;
484
+ let _ = queue
485
+ .complete_status(call_uuid, "failed", msg, Some(processing_trace_id))
486
+ .await;
469
487
  error!(call_id = %work.call_id, "{msg}");
470
488
  }
471
489
  }
472
490
  Ok(())
473
491
  }
474
492
 
493
+ // Insert trace ID into headers if none already present
494
+ fn ensure_traceparent(headers: &mut HashMap<String, String>) -> String {
495
+ if let Some(tp) = headers.get("traceparent") {
496
+ if let Some(trace_id) = tp.split('-').nth(1).filter(|s| s.len() == 32) {
497
+ return trace_id.to_string();
498
+ }
499
+ }
500
+ let trace_id = Uuid::new_v4().simple().to_string();
501
+ let span_id = &Uuid::new_v4().simple().to_string()[..16];
502
+ headers.insert(
503
+ "traceparent".to_string(),
504
+ format!("00-{trace_id}-{span_id}-01"),
505
+ );
506
+ trace_id
507
+ }
508
+
475
509
  /// Decode the payload, invoke the handler, and write the outcome (chunks then
476
510
  /// `end`, or `error`) to the call's result stream, updating meta status along
477
511
  /// the way.
@@ -481,13 +515,14 @@ async fn execute_and_write(
481
515
  conn: &mut ConnectionManager,
482
516
  work: QueuedCall,
483
517
  flush_timeout: Duration,
484
- metrics_pipeline: Option<&Arc<dyn MetricsPipeline>>,
518
+ metrics_pipeline: Option<&Arc<PublishingMetricsPipeline>>,
485
519
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
486
520
  let call_uuid = Uuid::parse_str(&work.call_id)?;
487
521
  let stream_key = queue.stream_key_for(call_uuid);
488
522
  let ipc_bytes = work.call.feather_bytes()?;
489
523
  let function_name = work.call.name.clone();
490
- let metadata = work.metadata.otel_headers.clone();
524
+ let mut metadata = work.metadata.otel_headers.clone();
525
+ let processing_trace_id = ensure_traceparent(&mut metadata);
491
526
  let started = Instant::now();
492
527
 
493
528
  let _ = queue.update_status(call_uuid, "running").await;
@@ -543,7 +578,13 @@ async fn execute_and_write(
543
578
  // so every executed call is recorded regardless of downstream Redis write
544
579
  // failure. Mirrors the catalog consumer's per-call emission.
545
580
  if let Some(p) = metrics_pipeline {
546
- crate::metrics::record_completed_call(p, &function_name, result.is_ok(), started.elapsed());
581
+ crate::metrics::record_completed_call(
582
+ p,
583
+ &function_name,
584
+ result.is_ok(),
585
+ started.elapsed(),
586
+ Mode::Async,
587
+ );
547
588
  }
548
589
 
549
590
  // Flush whatever the handler produced after the loop exits (on the fast path
@@ -558,19 +599,37 @@ async fn execute_and_write(
558
599
  Ok(()) => {
559
600
  stream_writer::write_end(conn, &stream_key, seq).await?;
560
601
  let _ = queue
561
- .complete_status(call_uuid, "completed", &format!("{flushed} chunk(s)"))
602
+ .complete_status(
603
+ call_uuid,
604
+ "completed",
605
+ &format!("{flushed} chunk(s)"),
606
+ Some(processing_trace_id),
607
+ )
562
608
  .await;
563
609
  info!(call_id = %work.call_id, chunks = flushed, "queued call completed");
564
610
  }
565
611
  Err(e) => {
566
- // Parity with the enqueue/poll path: only a message string is
567
- // available (no structured exc_type / traceback yet), so exc_type is
568
- // a fixed placeholder.
569
- stream_writer::write_error(conn, &stream_key, seq, "RemoteFunctionError", &e.0, None)
570
- .await?;
571
- let summary: String = e.0.chars().take(200).collect();
572
- let _ = queue.complete_status(call_uuid, "failed", &summary).await;
573
- error!(call_id = %work.call_id, error = %e, "queued call failed");
612
+ // The formatted Python traceback is available, but the concrete
613
+ // exception type is not carried separately across this boundary.
614
+ stream_writer::write_error(
615
+ conn,
616
+ &stream_key,
617
+ seq,
618
+ "RemoteFunctionError",
619
+ e.details(),
620
+ None,
621
+ )
622
+ .await?;
623
+ let summary: String = e.to_string().chars().take(200).collect();
624
+ let _ = queue
625
+ .complete_status(call_uuid, "failed", &summary, Some(processing_trace_id))
626
+ .await;
627
+ error!(
628
+ call_id = %work.call_id,
629
+ exception.stacktrace = e.details(),
630
+ "queued call failed: {}",
631
+ e.details()
632
+ );
574
633
  }
575
634
  }
576
635
  Ok(())
@@ -1,6 +1,8 @@
1
1
  use std::net::SocketAddr;
2
2
  use std::sync::Arc;
3
3
 
4
+ #[cfg(target_os = "linux")]
5
+ use chalk_metrics::PublishingMetricsPipeline;
4
6
  use chalk_remote_call_proto::chalk::runtime::v1::async_remote_call_service_server::AsyncRemoteCallServiceServer;
5
7
  use chalk_remote_call_proto::chalk::runtime::v1::remote_call_service_server::RemoteCallServiceServer;
6
8
  use chalk_remote_call_proto::chalk::runtime::v1::FILE_DESCRIPTOR_SET;
@@ -14,11 +16,14 @@ use crate::service::RemoteCallServiceImpl;
14
16
 
15
17
  const MAX_MESSAGE_SIZE: usize = 32 * 1024 * 1024; // 32 MB
16
18
 
19
+ const ENV_REMOTE_FUNCTION: &str = "CHALK_REMOTE_FUNCTION";
20
+
17
21
  pub async fn run_server(
18
22
  host: String,
19
23
  port: u16,
20
24
  python_handler: Arc<PythonHandler>,
21
25
  coalescing_queue: Option<Arc<CoalescingQueue>>,
26
+ #[cfg(target_os = "linux")] pipeline: Option<Arc<PublishingMetricsPipeline>>,
22
27
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
23
28
  // Parse host — ensure IPv6 addresses are bracketed for SocketAddr parsing
24
29
  let addr_str = if host.contains(':') && !host.starts_with('[') {
@@ -57,7 +62,14 @@ pub async fn run_server(
57
62
  // coalescing flag before `coalescing_queue` is moved into the streaming
58
63
  // service below; the async path refuses enqueue when batching is on.
59
64
  let batching_enabled = coalescing_queue.is_some();
60
- let async_svc = AsyncRemoteCallServiceImpl::new(python_handler.clone(), batching_enabled);
65
+ // Checks if remote function env var is set or not
66
+ let is_remote_function: bool = std::env::var(ENV_REMOTE_FUNCTION)
67
+ .is_ok_and(|v| v.eq_ignore_ascii_case("true") || v == "1");
68
+ let async_svc = AsyncRemoteCallServiceImpl::new(
69
+ python_handler.clone(),
70
+ batching_enabled,
71
+ is_remote_function,
72
+ );
61
73
 
62
74
  // Self-consume the Redis fn queue when this pod was provisioned with a
63
75
  // Redis URL (gRPC-only otherwise). The self-consumer shares the gRPC
@@ -65,20 +77,22 @@ pub async fn run_server(
65
77
  // inbound gRPC calls feed the same queue so they batch together against the
66
78
  // single batch-size/duration the user configured. When batching is off the
67
79
  // queue is `None` and the self-consumer dispatches per-call (streaming).
68
- //
69
- // Linux-only: the self-consumer + its metrics pipeline depend on chalk-queue
70
- // / chalk_metrics, since these don't compile on macos and windows.
80
+
71
81
  #[cfg(target_os = "linux")]
72
82
  if let Some(cfg) = crate::self_consumer::SelfConsumerConfig::from_env() {
73
83
  // Build the chalk_metrics pipeline once (mirrors the fnq-server/cc
74
84
  // main init) so the self-consumer can emit function-call metrics.
75
- let pipeline = crate::metrics::init_pipeline().await;
76
85
  let consumer_handler = python_handler.clone();
86
+ let consumer_pipeline = pipeline.clone();
77
87
  let consumer_coalescing = coalescing_queue.clone();
78
88
  tokio::spawn(async move {
79
- if let Err(e) =
80
- crate::self_consumer::run(cfg, consumer_handler, pipeline, consumer_coalescing)
81
- .await
89
+ if let Err(e) = crate::self_consumer::run(
90
+ cfg,
91
+ consumer_handler,
92
+ consumer_pipeline,
93
+ consumer_coalescing,
94
+ )
95
+ .await
82
96
  {
83
97
  tracing::error!(error = %e, "fnq self-consumer failed to start");
84
98
  }
@@ -89,6 +103,8 @@ pub async fn run_server(
89
103
  let svc = RemoteCallServiceImpl {
90
104
  python_handler,
91
105
  coalescing_queue,
106
+ #[cfg(target_os = "linux")]
107
+ metrics_pipeline: pipeline,
92
108
  };
93
109
 
94
110
  info!("Server starting on {}", addr);
@@ -1,6 +1,12 @@
1
1
  use std::collections::HashMap;
2
2
  use std::sync::Arc;
3
+ #[cfg(target_os = "linux")]
4
+ use std::time::Instant;
3
5
 
6
+ #[cfg(target_os = "linux")]
7
+ use chalk_metrics::metrics::tags::Mode;
8
+ #[cfg(target_os = "linux")]
9
+ use chalk_metrics::PublishingMetricsPipeline;
4
10
  use chalk_remote_call_proto::chalk::runtime::v1::remote_call_service_server::RemoteCallService;
5
11
  use chalk_remote_call_proto::chalk::runtime::v1::{CallFunctionRequest, CallFunctionResponse};
6
12
  use tokio_stream::wrappers::ReceiverStream;
@@ -18,6 +24,9 @@ pub struct RemoteCallServiceImpl {
18
24
  /// Optional coalescing queue. When `Some`, incoming calls are buffered and
19
25
  /// dispatched as a single batched handler invocation.
20
26
  pub coalescing_queue: Option<Arc<CoalescingQueue>>,
27
+ // Linux-gated metrics pipeline
28
+ #[cfg(target_os = "linux")]
29
+ pub metrics_pipeline: Option<Arc<PublishingMetricsPipeline>>,
21
30
  }
22
31
 
23
32
  #[tonic::async_trait]
@@ -90,21 +99,51 @@ impl RemoteCallService for RemoteCallServiceImpl {
90
99
 
91
100
  // Single-request path.
92
101
  let handler = self.python_handler.clone();
102
+ #[cfg(target_os = "linux")]
103
+ let metrics_pipeline = self.metrics_pipeline.clone();
93
104
  tokio::spawn(async move {
105
+ #[cfg(target_os = "linux")]
106
+ let started = Instant::now();
107
+
94
108
  let error_tx = tx.clone();
95
109
  match handler
96
- .call_streaming(all_bytes, function_name, metadata, peer, tx)
110
+ .call_streaming(all_bytes, function_name.clone(), metadata, peer, tx)
97
111
  .await
98
112
  {
99
- Ok(()) => {}
113
+ Ok(()) => {
114
+ #[cfg(target_os = "linux")]
115
+ if let Some(p) = &metrics_pipeline {
116
+ crate::metrics::record_completed_call(
117
+ p,
118
+ &function_name,
119
+ /* success = */ true,
120
+ started.elapsed(),
121
+ Mode::Sync,
122
+ )
123
+ }
124
+ }
100
125
  Err(e) => {
126
+ #[cfg(target_os = "linux")]
127
+ if let Some(p) = &metrics_pipeline {
128
+ crate::metrics::record_completed_call(
129
+ p,
130
+ &function_name,
131
+ /* success = */ false,
132
+ started.elapsed(),
133
+ Mode::Sync,
134
+ )
135
+ }
101
136
  if error_tx.is_closed() {
102
137
  // The only reason blocking_send fails is a dropped
103
138
  // receiver — the client went away mid-stream. Not a
104
139
  // handler failure; nothing to send.
105
140
  debug!("client disconnected during streaming response");
106
141
  } else {
107
- error!("Python handler error: {}", e);
142
+ error!(
143
+ exception.stacktrace = e.details(),
144
+ "Python handler error: {}",
145
+ e.details()
146
+ );
108
147
  let _ = error_tx
109
148
  .send(Err(Status::unknown(format!(
110
149
  "Exception raised during handler execution: {e}"
@@ -0,0 +1 @@
1
+ __version__ = "1.8.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 1.8.1
3
+ Version: 1.8.3
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -1 +0,0 @@
1
- __version__ = "1.8.1"