<@ULVA73B9P> I deployed a flow with concurrency li...
# ask-marvin
p
@Marvin I deployed a flow with concurrency limit 1 but the flow only keep pending for a while before start running without the previous running flow run finished or completed
m
thought for 380.7 seconds
Thanks for the report — this sounds like a deployment-level concurrency lease expiring, which can let another run start even though the first hasn’t finished. What should happen - With a deployment concurrency limit of 1, Prefect acquires a lease when a run moves SCHEDULED → PENDING and keeps renewing it until the run finishes. While that lease is active, other runs should be rescheduled (ENQUEUE) or cancelled (CANCEL_NEW), depending on your collision strategy. Why you might see a second run start early - Lease not renewed/expired: If the client/worker running the flow does not renew the lease (older client, network hiccup, or long startup), the server will reap the lease after its TTL (default ~300s). Once reaped, the limit appears available and another run can acquire it and proceed to RUNNING — even while the first run is still executing. - Runs are from different deployments: Deployment concurrency is per-deployment. Two different deployments of the same flow do not limit each other. - Using tag-based limits (task-level) instead of a deployment limit: Tag limits won’t stop flow runs from overlapping unless you add a gating task at the start. How to fix/stabilize 1) Verify the limit is actually set on the deployment - In UI: open the deployment and confirm Concurrency limit = 1. - CLI:
Copy code
prefect deployment inspect "your-flow/your-deployment"
2) Ensure your worker/runtime is on a recent Prefect client (needed to renew leases) - Use Prefect >= 3.4.11 on the worker image/venv running your flow. 3) Increase the grace period if infra startup is slow (Kubernetes/Docker can take >5–10 minutes) - prefect.yaml:
Copy code
deployments:
    - name: your-deployment
      concurrency_limit:
        limit: 1
        grace_period_seconds: 900   # e.g., 15 minutes
- Or via Python:
Copy code
flow.from_source(...).deploy(
      name="your-deployment",
      concurrency_limit={"limit": 1, "grace_period_seconds": 900},
  )
- As a server-wide fallback, you can raise PREFECT_SERVER_CONCURRENCY_INITIAL_DEPLOYMENT_LEASE_DURATION to match your environment, but per-deployment is usually best. 4) If you need a single limit across multiple deployments - Deployment limits are per-deployment. To enforce a cross-deployment limit of 1, add a tiny “gate” task at the start of the flow with a shared tag and create a tag limit of 1:
Copy code
# create once
  prefect concurrency-limit create "flow-gate" --concurrency-limit 1
Copy code
from prefect import flow, task

  @task(tags=["flow-gate"])
  def gate():
      return

  @flow
  def my_flow(...):
      gate()         # ensures only one flow instance proceeds at a time
      ...
A few quick checks to help pinpoint the cause - Are both overlapping runs from the same deployment? - How long do runs sit in PENDING before starting? (>5 minutes often indicates lease expiry unless renewed) - What Prefect version is installed in your worker/runtime image? - Any logs about “concurrency lease” renewal or expiry around the time the second run started? If you share those details, I can help confirm whether this is a lease-expiry issue and suggest the exact change needed.
p
@Marvin it actually start the new flow run after a while, sometimes as fast as 4 min, sometimes after 15 min
m
thought for 47.4 seconds
Got it — the “starts a new run after ~4 min or ~15 min” pattern is a smoking gun for a deployment concurrency lease expiring. What that means - Prefect acquires a deployment-level concurrency lease when the run moves SCHEDULED → PENDING. - If the flow process doesn’t start renewing that lease (older client, slow infra start, or network issues), the server reaps it when the lease TTL expires. - Defaults are typically ~5 minutes (300s). If you’ve configured a longer grace period (e.g., 900s), you’ll see ~15 minutes. - Once the lease is reaped, the slot looks free and another run can start — even though the first run is still executing. Why your timings vary - ~4–5 minutes: aligns with the default 300s lease TTL - ~15 minutes: aligns with a 900s grace period (either in the deployment config or server setting) - The variation suggests different deployments/configs, or a recent change to grace_period_seconds, or sometimes the client renews for a bit then stops. How to fix it 1) Ensure the flow runtime can renew the lease - Run the flow on Prefect >= 3.4.11 in the worker/container that executes the flow. - Verify connectivity from the flow process to the API (renewals require API access throughout the run). 2) Increase the initial grace period if infra startup is slow - If you’re on Kubernetes/Docker and startup can exceed several minutes, raise the deployment’s grace period so the client has time to start and begin renewing:
Copy code
deployments:
    - name: your-deployment
      concurrency_limit:
        limit: 1
        grace_period_seconds: 900   # e.g., 15 minutes
Or in code:
Copy code
flow.from_source(...).deploy(
      name="your-deployment",
      concurrency_limit={"limit": 1, "grace_period_seconds": 900},
  )
- You can also raise the server-wide default with PREFECT_SERVER_CONCURRENCY_INITIAL_DEPLOYMENT_LEASE_DURATION, but per-deployment is usually better. 3) Confirm it’s the same deployment - Concurrency is per-deployment. If two different deployments exist, they won’t limit each other. 4) Optional: add logging to verify renewals - Set
PREFECT_LOGGING_LEVEL=DEBUG
in the flow environment to see messages about acquiring/renewing the “deployment concurrency lease.” You’ll spot renew failures or expiry. Quick checks to pinpoint - What Prefect version is installed in the image/venv that runs the flow? - Are both overlapping runs from the same deployment? - What is your deployment’s
grace_period_seconds
(or the server default)? - Any DEBUG logs about “maintaining” or “failed to renew” the concurrency lease? If you share: - the deployment’s concurrency config, - your worker type (Kubernetes/Docker/Process) and Prefect version in the runtime, - and whether the overlapping runs are from the same deployment, I can confirm the exact culprit and give a targeted fix.
p
@Marvin concurrency_limit Input should be a valid integer [type=int_type, input_value={'limit': 1, 'grace_period_seconds': 36000}, input_type=dict] For further information visit https://errors.pydantic.dev/2.12/v/int_type 044908.257 | ERROR | Flow run 'chirpy-oryx' - Error occurred while processing prescription 2: 1 validation error for RunnerDeployment concurrency_limit Input should be a valid integer [type=int_type, input_value={'limit': 1, 'grace_period_seconds': 36000}, input_type=dict] For further information visit https://errors.pydantic.dev/2.12/v/int_type 044908.278 | ERROR | Flow run 'chirpy-oryx' - Error occurred while processing prescription 2: 1 validation error for RunnerDeployment concurrency_limit Input should be a valid integer [type=int_type, input_value={'limit': 1, 'grace_period_seconds': 36000}, input_type=dict] For further information visit https://errors.pydantic.dev/2.12/v/int_type 044908.297 | ERROR | Flow run 'chirpy-oryx' - Error occurred while processing prescription 2: 1 validation error for RunnerDeployment concurrency_limit Input should be a valid integer [type=int_type, input_value={'limit': 1, 'grace_period_seconds': 36000}, input_type=dict]
m
thought for 101.8 seconds
Thanks — that error explains it. You passed a dict to
concurrency_limit
in a place that only accepts an integer, which is why Pydantic says “Input should be a valid integer … input_type=dict”. How to set it correctly Pick the path you’re using: - If you’re deploying in Python with Flow.deploy/to_deployment Pass a ConcurrencyLimitConfig object (not a raw dict):
Copy code
from prefect import flow
  from prefect.client.schemas.objects import ConcurrencyLimitConfig, ConcurrencyLimitStrategy

  @flow
  def my_flow():
      ...

  my_flow.deploy(
      name="my-deployment",
      work_pool_name="my-pool",
      concurrency_limit=ConcurrencyLimitConfig(
          limit=1,
          grace_period_seconds=36000,            # 10 hours, valid (60–86400)
          collision_strategy=ConcurrencyLimitStrategy.ENQUEUE,
      ),
  )
- If you’re using prefect.yaml with prefect deploy A dict is valid here; the CLI will split it into the right fields:
Copy code
deployments:
    - name: my-deployment
      work_pool:
        name: my-pool
      concurrency_limit:
        limit: 1
        grace_period_seconds: 36000
        collision_strategy: enqueue   # or cancel_new
- If you’re constructing RunnerDeployment directly Set the int and options separately:
Copy code
from prefect.deployments.runner import RunnerDeployment
  from prefect.client.schemas.objects import ConcurrencyOptions, ConcurrencyLimitStrategy

  deployment = RunnerDeployment(
      name="my-deployment",
      concurrency_limit=1,
      concurrency_options=ConcurrencyOptions(
          grace_period_seconds=36000,
          collision_strategy=ConcurrencyLimitStrategy.ENQUEUE,
      ),
      # ... other fields ...
  )
Why this matters for your overlap issue - Your earlier overlaps at ~4–15 minutes match a lease expiring. Increasing grace_period_seconds is the right fix, but you need to pass it via ConcurrencyLimitConfig (Python) or the YAML dict (CLI), not as a raw dict to
concurrency_limit
in Python. Two final checks - Ensure the runtime environment that executes the flow is Prefect >= 3.4.11 so it can renew the lease for the entire run. - Confirm both overlapping runs are from the same deployment; limits are per-deployment. If you share how you’re creating the deployment (Python vs prefect.yaml), I can tailor the exact snippet to your setup.