Overview
けっこう昔にこんな投げっぱなしな感じの記事書いたんですけど、
https://qiita.com/arc279/items/d400c92d6019afc665eb
その派生というか、 docker 内のプロセスが OOMKiller に始末されるのを避けて MemoryError
を捕捉する方法です。
事前準備と予備知識
とりあえずメモリを500MB に制限して docker で python を起動します。
docker周り
たまたま手元にあったのが python:3.7.6-slim
だったので別に深い意味はないです。
version: '3.9'
services:
node:
image: python:3.7.6-slim
volumes:
- .:/mnt
deploy:
resources:
limits:
memory: 500M
reservations:
memory: 500M
tty: true
command: bash
# 起動して
$ docker-compose up
# コンテナに入る
$ docker-compose exec node bash
コンテナ内で確認
docker のメモリ制限は cgroup で制限されてるので、確認はこう。
root@33fc41fd23a2:/# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
524288000
500MB に制限されてますね。
memory_swap を設定しない場合の swap はメモリ指定の2倍になってるそうです。
ちなみに
free
とか /proc/meminfo
には反映されないので注意。
root@33fc41fd23a2:/# cat /proc/meminfo | grep Mem
MemTotal: 14334276 kB
MemFree: 13063124 kB
MemAvailable: 13221092 kB
root@33fc41fd23a2:/# free
total used free shared buff/cache available
Mem: 14334276 468032 12900708 409740 965536 13210688
Swap: 1048572 43808 1004764
python でヒープサイズの上限
以下、 前述のコンテナ内に入っての処理です。
python を起動します。
特に指定しないと無制限になってるので、
>>> import resource
>>> resource.getrlimit(resource.RLIMIT_DATA)
(-1, -1)
メモリ 500MB の状況で 4GB くらい確保しようとすると
>>> try:
... _ = bytearray(4000 * 1024 ** 2)
... except Exception as e:
... print(type(e), e)
...
Killed
except できず問答無用で OOM Killer に始末されて死んじゃいます。
ヒープサイズに制限をかける
で、無言で死んでしまうとちょっと困るので、これをなんとか捕捉したい。
上限を設定します
>>> import resource
>>> resource.setrlimit(resource.RLIMIT_DATA, (524288000, -1))
>>> print(resource.getrlimit(resource.RLIMIT_DATA))
(524288000, -1)
で、再度実行
>>> try:
... _ = bytearray(4000 * 1024 ** 2)
... except Exception as e:
... print(type(e), e)
...
<class 'MemoryError'>
except で補足できました。
ちなみに MemoryError
は無口なので str(e)
しても特に文言がありません。
なので type(e)
しないと分かりづらいので注意。
上記をまとめると
ヒープサイズの上限を /sys/fs/cgroup/memory/memory.limit_in_bytes
の値に設定してやれば良さそうです。
import resource
# 500MB しかなくても 4GB確保したい
alloc_size = 4000 * 1024 ** 2
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r") as fp:
memory_limit_in_bytes = int(fp.read())
print("alloc_size", alloc_size)
print("/sys/fs/cgroup/memory/memory.limit_in_bytes", memory_limit_in_bytes)
resource.setrlimit(resource.RLIMIT_DATA, (memory_limit_in_bytes, -1))
print(resource.getrlimit(resource.RLIMIT_DATA))
try:
_ = bytearray(alloc_size)
print("OK")
except MemoryError as e:
print(type(e), e)
root@33fc41fd23a2:/mnt# python trap_memory_error.py
alloc_size 4194304000
/sys/fs/cgroup/memory/memory.limit_in_bytes 524288000
(524288000, -1)
<class 'MemoryError'>
なお、ヒープ上限を設定しないと
上記の resource.setrlimit
の行をコメントアウトして実行すると、普通に OOM Killer に始末されて死にます。
root@33fc41fd23a2:/mnt# python no-trap_memory_error.py
alloc_size 4194304000
/sys/fs/cgroup/memory/memory.limit_in_bytes 524288000
(-1, -1)
Killed
っていう話。
プロセスの確保可能なヒープサイズに上限を設定することでプロセス内で捕捉するのが肝なので、
同様の手段があれば python 以外でもできると思います。