Troubleshooting and Common Pitfalls
This document outlines some of the common pitfalls you may encounter when getting set up with huey. It is arranged in a problem/solution format.
- Tasks not running
First step is to increase logging verbosity by running the consumer with
--verbose. You can also specify a logfile using the--logfileoption.Check for any exceptions. The most common cause of tasks not running is that they are not being loaded, in which case you will see
HueyException“XXX not found in TaskRegistry” errors.- “HueyException: XXX not found in TaskRegistry” in log file
Exception occurs when a task is called by a task producer, but is not imported by the consumer. To fix this, ensure that by loading the
Hueyobject, you also import any decorated functions as well.For more information on how tasks are imported, see the import documentation.
- “Error importing XXX” when starting consumer
This error message occurs when the module containing the configuration specified cannot be loaded (not on the pythonpath, mistyped, etc). One quick way to check is to open up a python shell and try to import the configuration.
Example syntax:
huey_consumer.py main_module.huey- Tasks not returning results
Ensure that you have not accidentally specified
results=Falsewhen instantiating yourHueyobject.Additionally note that, by default, Huey does not store
Nonein the result-store. So if your task returnsNone, Huey will discard the result. If you need to block or detect whether a task has finished, it is recommended that you return a non-Nonevalue or in extreme circumstances you can initialize Huey withstore_none=True(though this can quickly fill up your result store and is only recommended for users who are very familiar with Huey).- Task result is always None even though the task returns a value
This is often caused by reading the result before the consumer has executed the task. When a result is not yet available,
result.get()returnsNone. Useresult.get(blocking=True, timeout=5)to block until the result is ready.Another subtle cause: Huey’s result store is destructive by default. Once you call
result.get()orresult(), the value is removed from the store. Calling it a second time will returnNone. If you need to read a result multiple times, passpreserve=True:val = result.get(preserve=True) # Value is retained in the store. val = result.get(preserve=True) # Same value returned again.
- Task result is stale after a retry
When a task fails and is retried, Huey stores the error from the first attempt. If the retry succeeds, the new result overwrites the old one. However, if your code already called
result.get()and received the error, theResultobject caches the error locally. You must callresult.reset()to clear the local cache before reading again:result = flaky_task() try: result.get(blocking=True, timeout=5) except TaskException: # Task failed. Wait for the retry to finish, then: result.reset() value = result.get(blocking=True, timeout=30)
- Scheduled tasks are not being run at the correct time
Check the time on the server the consumer is running on - if different from the producer this may cause problems. Huey uses UTC internally by default, and naive datetimes will be converted from local time to UTC (if local time happens to not be UTC).
- Cronjobs are not being run
The consumer and scheduler run in UTC by default.
- Periodic tasks running twice (or more)
When running multiple consumers, only one consumer should be configured to enqueue periodic tasks. All other consumers should be started with the
-n/--no-periodicflag. Without this, each consumer will independently enqueue the same periodic tasks, leading to duplicate executions.See Multiple Consumers for details.
- Greenlet workers seem stuck
If you wish to use the Greenlet worker type, you need to be sure to monkeypatch in your application’s entrypoint. At the top of your
mainmodule, you can add the following code:from gevent import monkey; monkey.patch_all(). Furthermore, if your tasks are CPU-bound,geventcan appear to lock up because it only supports cooperative multi-tasking (as opposed to pre-emptive multi-tasking when using threads). For Django, it is necessary to apply the patch inside themanage.pyscript. See the Django docs section for the code.- Task locks are stuck after consumer crash
If the consumer is killed abruptly (
SIGKILL, power loss, OOM killer) while executing a locked task, the lock key remains in the storage backend and the task will appear permanently locked.Solutions:
Start the consumer with
-f/--flush-locksto clear all known locks on startup.If you have locks created inside context managers (not as decorators), they may not be automatically discovered. Use
-L/--extra-locksto specify their names:huey_consumer.py app.huey -f -L my-lock-name.Manually clear a specific lock from your application code:
huey.lock_task('my-lock-name').clear() # Or flush all discovered locks: huey.flush_locks()
See
Huey.lock_task(),Huey.flush_locks(), and the-fconsumer option for more details.- Task function not found after renaming or moving
Huey registers tasks by their module path and function name (e.g.,
myapp.tasks.process_order). If you rename a function or move it to another module while tasks are still queued, the consumer will fail to deserialize those tasks and raise aHueyException.Solutions:
Drain the queue before deploying (let the old consumer process all remaining tasks before starting the new code).
Use the
nameparameter on thetask()decorator to give the task a stable name that survives refactoring:@huey.task(name='myapp.tasks.process_order') def handle_order(order_id): ...
- Tasks running out of order
With
SqliteHueyandstrict_fifo=False(the default), SQLite may reuse rowids for deleted tasks, which can cause tasks to be dequeued in a different order than the order in which they were enqueued.If strict FIFO ordering is important, initialize with
strict_fifo=True:huey = SqliteHuey('my-app', filename='huey.db', strict_fifo=True)
With Redis storage, the default
RedisHueyuses a list and preserves insertion order.PriorityRedisHueyorders by priority score.- Result store growing without bound (Redis)
When using
RedisHuey, task results are stored in a Redis hash. Results are only removed when read (viaresult.get()orhuey.result()). If your tasks return values that nobody reads, the hash will grow indefinitely.Solutions:
Switch to
RedisExpireHuey, which stores results as individual keys with a TTL. Unread results are automatically cleaned up by Redis.Have your tasks return
Noneif you do not need the result.Initialize Huey with
results=Falseif you never read results.Periodically read and discard results from the application side.
- Consumer seems slow / tasks have high latency
Several things to check:
Worker count: The default is 1 worker. Increase it with
-w. Most applications benefit from at least 2-4 workers.Blocking vs polling:
RedisHueydefaults toblocking=True, which reacts instantly to new messages.SqliteHueyuses polling. If you are using polling, the-d(delay) and-b(backoff) parameters control how frequently Huey checks for new messages.Scheduler interval: The default scheduler interval is 1 second (
-s 1) (lowest possible value). If you have very few scheduled tasks and want to save CPU, you can increase the interval to 10 or 30 seconds.Worker type: For CPU-bound tasks, use
-k process. For IO-bound tasks, consider-k greenletwith many workers.Verbose logging: Run with
-vto see exactly when tasks are dequeued, executed, and completed. Large gaps between “dequeued” and “executing” indicate worker saturation.
- Memory growing in consumer process
When using process workers (
-k process), child processes are not recycled – they run for the lifetime of the consumer. If tasks allocate large data structures, memory may accumulate over time.Solutions:
Specify
-M num/--max-tasks=numto force workers to restart after executing the given numbernumtasks. It’s harsh but effective.Restart the consumer periodically (
supervisord’sautorestartor a cron job that sendsSIGHUP).Use thread or greenlet workers, which share memory with the main process and are subject to garbage collection.
Structure tasks to process data in smaller chunks rather than loading everything into memory at once.
- “TypeError: can’t pickle …” or “PicklingError”
Task arguments and return values must be serializable by
pickle. Common offenders include: database connections, open file handles, lambda functions, generators, and class instances with unpicklable attributes (e.g., locks, sockets).Solutions:
Pass IDs, keys, or file paths instead of objects. Look up the object inside the task function.
If you need a database connection, use
on_startup()orcontext_task()to initialize it inside the worker.For custom classes, implement
__getstate__and__setstate__, or convert to a plaindictbefore returning.If you need a different serialization format entirely, subclass
Serializerand override_serialize/_deserialize.
- Testing projects using Huey
Use
immediate=True:test_mode = os.environ.get('TEST_MODE') # When immediate=True, Huey will default to using an in-memory # storage layer. huey = RedisHuey(immediate=test_mode) # Alternatively, you can set the `immediate` attribute: huey.immediate = True if test_mode else False
For more details, see the testing guidelines section of the guide.
- Task decorated with
@huey.task()blocks when called If calling a task-decorated function blocks instead of returning a result handle immediately, check whether
immediate=Trueis set on your Huey instance. In immediate mode, tasks are executed synchronously by the caller. This is by design for testing and debugging, but if you see this behavior unexpectedly, ensurehuey.immediateisFalsein production.