SemLock leak: fill /dev/shm when creating and deleting multiple Pools
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 28/100
- Issue type
- Bug
- Clarity
- Needs clarification
- Activity status
- Stale
- Tech stack
- python
- Domain
- operating-systems
Research direction
Run the provided Pool reproduction with billiard and multiprocessing to confirm the /dev/shm difference. Then compare the SemLock creation path in billiard/synchronize.py with the queue and pool paths mentioned in billiard/queues.py and billiard/pool.py. Done means repeatedly creating and deleting Pools no longer leaves deleted mappings or consumes additional /dev/shm space.
Written by the indexing model from the issue text.
Description
Initial issue:
OSError: [Errno 28] No space left on device
File "xxx.py", line 1029, in main
pool = Pool(nb_processes)
File "billiard/pool.py", line 995, in __init__
self._setup_queues()
File "billiard/pool.py", line 1364, in _setup_queues
self._inqueue = self._ctx.SimpleQueue()
File "billiard/context.py", line 150, in SimpleQueue
return SimpleQueue(ctx=self.get_context())
File "billiard/queues.py", line 377, in __init__
self._rlock = ctx.Lock()
File "billiard/context.py", line 105, in Lock
return Lock(ctx=self.get_context())
File "billiard/synchronize.py", line 182, in __init__
SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)
File "billiard/synchronize.py", line 72, in __init__
kind, value, maxvalue, self._make_name(), unlink_now,
(with billiard==3.6.1.0)
Reproduction
import billiard
import multiprocessing
import os
import subprocess
DEBUG = False
def check_shm():
out = subprocess.check_output(['ls -la /proc/{}/map_files/ | grep /dev/shm/ || :'.format(os.getpid())], shell=True)
lines = out.splitlines()
if DEBUG:
print('Found {} files in /dev/shm from current process:'.format(len(lines)))
for line in lines:
print(line.decode())
df_out = subprocess.check_output(["df /dev/shm | tail -1 | awk '{print $3}'"], shell=True)
df_lines = df_out.splitlines()
return (len(lines), int(df_lines[0].decode()))
def repro(Pool):
for i in range(1, 10):
name = f'{Pool.__module__}.{Pool.__name__} {i}'
print(f'{name} start {check_shm()}')
p = Pool(1)
print(f'{name} created {check_shm()}')
p.close()
p.join()
print(f'{name} joined {check_shm()}')
del p
print(f'{name} deleted {check_shm()}')
repro(multiprocessing.Pool)
repro(billiard.Pool)
Result:
multiprocessing.context.Pool 1 start (0, 0)
multiprocessing.context.Pool 1 created (4, 16)
multiprocessing.context.Pool 1 joined (4, 16)
multiprocessing.context.Pool 1 deleted (0, 0)
multiprocessing.context.Pool 2 start (0, 0)
multiprocessing.context.Pool 2 created (4, 16)
multiprocessing.context.Pool 2 joined (4, 16)
multiprocessing.context.Pool 2 deleted (0, 0)
multiprocessing.context.Pool 3 start (0, 0)
multiprocessing.context.Pool 3 created (4, 16)
multiprocessing.context.Pool 3 joined (4, 16)
multiprocessing.context.Pool 3 deleted (0, 0)
multiprocessing.context.Pool 4 start (0, 0)
multiprocessing.context.Pool 4 created (4, 16)
multiprocessing.context.Pool 4 joined (4, 16)
multiprocessing.context.Pool 4 deleted (0, 0)
multiprocessing.context.Pool 5 start (0, 0)
multiprocessing.context.Pool 5 created (4, 16)
multiprocessing.context.Pool 5 joined (4, 16)
multiprocessing.context.Pool 5 deleted (0, 0)
multiprocessing.context.Pool 6 start (0, 0)
multiprocessing.context.Pool 6 created (4, 16)
multiprocessing.context.Pool 6 joined (4, 16)
multiprocessing.context.Pool 6 deleted (0, 0)
multiprocessing.context.Pool 7 start (0, 0)
multiprocessing.context.Pool 7 created (4, 16)
multiprocessing.context.Pool 7 joined (4, 16)
multiprocessing.context.Pool 7 deleted (0, 0)
multiprocessing.context.Pool 8 start (0, 0)
multiprocessing.context.Pool 8 created (4, 16)
multiprocessing.context.Pool 8 joined (4, 16)
multiprocessing.context.Pool 8 deleted (0, 0)
multiprocessing.context.Pool 9 start (0, 0)
multiprocessing.context.Pool 9 created (4, 16)
multiprocessing.context.Pool 9 joined (4, 16)
multiprocessing.context.Pool 9 deleted (0, 0)
billiard.context.Pool 1 start (0, 0)
billiard.context.Pool 1 created (5, 20)
billiard.context.Pool 1 joined (5, 20)
billiard.context.Pool 1 deleted (5, 20)
billiard.context.Pool 2 start (5, 20)
billiard.context.Pool 2 created (10, 40)
billiard.context.Pool 2 joined (10, 40)
billiard.context.Pool 2 deleted (10, 40)
billiard.context.Pool 3 start (10, 40)
billiard.context.Pool 3 created (15, 60)
billiard.context.Pool 3 joined (15, 60)
billiard.context.Pool 3 deleted (15, 60)
billiard.context.Pool 4 start (15, 60)
billiard.context.Pool 4 created (20, 80)
billiard.context.Pool 4 joined (20, 80)
billiard.context.Pool 4 deleted (20, 80)
billiard.context.Pool 5 start (20, 80)
billiard.context.Pool 5 created (25, 100)
billiard.context.Pool 5 joined (25, 100)
billiard.context.Pool 5 deleted (25, 100)
billiard.context.Pool 6 start (25, 100)
billiard.context.Pool 6 created (30, 120)
billiard.context.Pool 6 joined (30, 120)
billiard.context.Pool 6 deleted (30, 120)
billiard.context.Pool 7 start (30, 120)
billiard.context.Pool 7 created (35, 140)
billiard.context.Pool 7 joined (35, 140)
billiard.context.Pool 7 deleted (35, 140)
billiard.context.Pool 8 start (35, 140)
billiard.context.Pool 8 created (40, 160)
billiard.context.Pool 8 joined (40, 160)
billiard.context.Pool 8 deleted (40, 160)
billiard.context.Pool 9 start (40, 160)
billiard.context.Pool 9 created (45, 180)
billiard.context.Pool 9 joined (45, 180)
billiard.context.Pool 9 deleted (45, 180)
Analysis
Files in /dev/shm leak, in fact they are marked as deleted (they don't appear in ls /dev/shm), only memory mapped to the process.
df clearly show the disk space is indeed used and filling up.
I tried with various versions of python and billiard:
- python: 2.7.17, 3.6.9, 3.8.0: always the same issue
- billiard: 3.6.1.0, 3.5.0.5, 3.3.0.23, 3.3.0.17 (before I get an unrelated error)
This is an issue in docker/kubernetes where the default /dev/shm is 64MB (for each container) which can be filled up.
Source analysis
I tried to compare billiard and multiprocessing sources and could not find anything different in semaphore.c or in the python stack where the SemLock is created...
I then assumed some object was referencing the SemLock with billiard, using gc.get_referrers(): I could not find any difference with multiprocessing...
- Dominant language
- Python
- Stars
- 436
- Forks
- 273
- Avg merge
- 4d 19h
- Merged PRs (30d)
- 5
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from celery/billiard
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
return in finally can swallow exceptionsMay be free again @auvipy claimed this 602 days ago, and no pull request is open. Open
celery/billiard#414 · 6 comments · 3 reactions · 1 assignee ·
-
Handling numpyOpen
Difficulty 4/5 3-5 days Newbie friendliness 25/100
-
Difficulty 3/5 1-2 days Newbie friendliness 35/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
AI bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
AUTO-MAS-Project/AUTO-MAS#1960 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
btclib-org/btclib-wallet#872 ·
Maintainers usually reply within 1 day
-
[Bug]: Scheduled returns scan all accepted minion keys causing extreme loadPossibly taken A pull request linked to this issue is open or already merged. Openbug needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
saltstack/salt#70789 · 1 comment ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
btclib-org/btclib#3093 ·
Maintainers usually reply within 1 day