|
1 | 1 | use crate::{
|
2 |
| - Worker, advance_fut, |
| 2 | + PollWorkflowOptions, Worker, advance_fut, |
3 | 3 | internal_flags::CoreInternalFlags,
|
4 | 4 | job_assert,
|
5 | 5 | replay::TestHistoryBuilder,
|
@@ -3150,3 +3150,153 @@ async fn pass_timer_summary_to_metadata() {
|
3150 | 3150 | .unwrap();
|
3151 | 3151 | worker.run_until_done().await.unwrap();
|
3152 | 3152 | }
|
| 3153 | + |
| 3154 | +#[tokio::test] |
| 3155 | +async fn both_normal_and_sticky_pollers_poll_concurrently() { |
| 3156 | + struct Counters { |
| 3157 | + // How many time PollWorkflowTaskQueue has been called |
| 3158 | + normal_poll_count: AtomicUsize, |
| 3159 | + sticky_poll_count: AtomicUsize, |
| 3160 | + |
| 3161 | + // How many pollers are currently active (i.e. PollWorkflowTaskQueue |
| 3162 | + // has been called, but not the corresponding CompleteWorkflowTask) |
| 3163 | + normal_slots_active_count: AtomicUsize, |
| 3164 | + sticky_slots_active_count: AtomicUsize, |
| 3165 | + |
| 3166 | + // Max number of pollers that were active at the same time |
| 3167 | + max_total_slots_active_count: AtomicUsize, |
| 3168 | + max_normal_slots_active_count: AtomicUsize, |
| 3169 | + max_sticky_slots_active_count: AtomicUsize, |
| 3170 | + } |
| 3171 | + |
| 3172 | + let counters = Arc::new(Counters { |
| 3173 | + normal_poll_count: AtomicUsize::new(0), |
| 3174 | + sticky_poll_count: AtomicUsize::new(0), |
| 3175 | + normal_slots_active_count: AtomicUsize::new(0), |
| 3176 | + sticky_slots_active_count: AtomicUsize::new(0), |
| 3177 | + max_total_slots_active_count: AtomicUsize::new(0), |
| 3178 | + max_normal_slots_active_count: AtomicUsize::new(0), |
| 3179 | + max_sticky_slots_active_count: AtomicUsize::new(0), |
| 3180 | + }); |
| 3181 | + |
| 3182 | + // Create actual workflow task responses to return from polls |
| 3183 | + let mut task_responses = (1..100).map(|i| { |
| 3184 | + hist_to_poll_resp( |
| 3185 | + &canned_histories::single_timer(&format!("timer-{i}")), |
| 3186 | + format!("wf-{i}"), |
| 3187 | + 1.into(), |
| 3188 | + ) |
| 3189 | + .resp |
| 3190 | + }); |
| 3191 | + |
| 3192 | + let mut mock_client = mock_workflow_client(); |
| 3193 | + |
| 3194 | + // Track normal vs sticky poll requests and return actual workflow tasks |
| 3195 | + let cc = Arc::clone(&counters); |
| 3196 | + mock_client |
| 3197 | + .expect_poll_workflow_task() |
| 3198 | + .returning(move |_, opts: PollWorkflowOptions| { |
| 3199 | + let mut task_response = task_responses.next().unwrap_or_default(); |
| 3200 | + |
| 3201 | + // FIXME: Atomics initially made sense, but this has grown ugly, and there's probably |
| 3202 | + // cases where this may produce incorrect results due to race in operation ordering |
| 3203 | + // (really didn't put any thought into this). We also can't have |
| 3204 | + if opts.sticky_queue_name.is_none() { |
| 3205 | + // Normal queue poll |
| 3206 | + cc.normal_poll_count.fetch_add(1, Ordering::Relaxed); |
| 3207 | + cc.normal_slots_active_count.fetch_add(1, Ordering::Relaxed); |
| 3208 | + cc.max_normal_slots_active_count.fetch_max( |
| 3209 | + cc.normal_slots_active_count.load(Ordering::Relaxed), |
| 3210 | + Ordering::AcqRel, |
| 3211 | + ); |
| 3212 | + cc.max_total_slots_active_count.fetch_max( |
| 3213 | + cc.normal_slots_active_count.load(Ordering::Relaxed) |
| 3214 | + + cc.sticky_slots_active_count.load(Ordering::Relaxed), |
| 3215 | + Ordering::AcqRel, |
| 3216 | + ); |
| 3217 | + |
| 3218 | + task_response.task_token = [task_response.task_token, b"normal".to_vec()].concat(); |
| 3219 | + } else { |
| 3220 | + // Sticky queue poll |
| 3221 | + cc.sticky_poll_count.fetch_add(1, Ordering::Relaxed); |
| 3222 | + cc.sticky_slots_active_count.fetch_add(1, Ordering::Relaxed); |
| 3223 | + cc.max_sticky_slots_active_count.fetch_max( |
| 3224 | + cc.sticky_slots_active_count.load(Ordering::Acquire), |
| 3225 | + Ordering::AcqRel, |
| 3226 | + ); |
| 3227 | + cc.max_total_slots_active_count.fetch_max( |
| 3228 | + cc.normal_slots_active_count.load(Ordering::Relaxed) |
| 3229 | + + cc.sticky_slots_active_count.load(Ordering::Relaxed), |
| 3230 | + Ordering::AcqRel, |
| 3231 | + ); |
| 3232 | + |
| 3233 | + task_response.task_token = [task_response.task_token, b"sticky".to_vec()].concat(); |
| 3234 | + } |
| 3235 | + |
| 3236 | + // Return actual workflow task responses |
| 3237 | + Ok(task_response) |
| 3238 | + }); |
| 3239 | + |
| 3240 | + let cc = Arc::clone(&counters); |
| 3241 | + mock_client |
| 3242 | + .expect_complete_workflow_task() |
| 3243 | + .returning(move |completion| { |
| 3244 | + if completion.task_token.0.ends_with(b"normal") { |
| 3245 | + cc.normal_slots_active_count.fetch_sub(1, Ordering::Relaxed); |
| 3246 | + } else { |
| 3247 | + cc.sticky_slots_active_count.fetch_sub(1, Ordering::Relaxed); |
| 3248 | + } |
| 3249 | + Ok(Default::default()) |
| 3250 | + }); |
| 3251 | + |
| 3252 | + let worker = Worker::new( |
| 3253 | + test_worker_cfg() |
| 3254 | + .max_cached_workflows(500_usize) // We need cache, but don't want to deal with evictions |
| 3255 | + .max_outstanding_workflow_tasks(2_usize) |
| 3256 | + .workflow_task_poller_behavior(PollerBehavior::SimpleMaximum(2_usize)) |
| 3257 | + .nonsticky_to_sticky_poll_ratio(0.2) |
| 3258 | + .no_remote_activities(true) |
| 3259 | + .build() |
| 3260 | + .unwrap(), |
| 3261 | + Some("stickytq".to_string()), |
| 3262 | + Arc::new(mock_client), |
| 3263 | + None, |
| 3264 | + ); |
| 3265 | + |
| 3266 | + for _ in 1..50 { |
| 3267 | + let activation = worker.poll_workflow_activation().await.unwrap(); |
| 3268 | + let _ = worker |
| 3269 | + .complete_workflow_activation(WorkflowActivationCompletion::empty(activation.run_id)) |
| 3270 | + .await; |
| 3271 | + } |
| 3272 | + |
| 3273 | + assert!( |
| 3274 | + counters.normal_poll_count.load(Ordering::Relaxed) > 0, |
| 3275 | + "Normal poller should have been called at least once" |
| 3276 | + ); |
| 3277 | + assert!( |
| 3278 | + counters.sticky_poll_count.load(Ordering::Relaxed) > 0, |
| 3279 | + "Sticky poller should have been called at least once" |
| 3280 | + ); |
| 3281 | + assert!( |
| 3282 | + counters |
| 3283 | + .max_normal_slots_active_count |
| 3284 | + .load(Ordering::Relaxed) |
| 3285 | + >= 1, |
| 3286 | + "Normal poller should have been active at least once" |
| 3287 | + ); |
| 3288 | + assert!( |
| 3289 | + counters |
| 3290 | + .max_sticky_slots_active_count |
| 3291 | + .load(Ordering::Relaxed) |
| 3292 | + >= 1, |
| 3293 | + "Sticky poller should have been active at least once" |
| 3294 | + ); |
| 3295 | + assert_eq!( |
| 3296 | + counters |
| 3297 | + .max_total_slots_active_count |
| 3298 | + .load(Ordering::Relaxed), |
| 3299 | + 2, |
| 3300 | + "At peak, there should be exactly 2 pollers active at the same time" |
| 3301 | + ); |
| 3302 | +} |
0 commit comments