Bound warm workers' host memory and account local RAM
Docs / docs (push) Successful in 19s
Playwright Tests / test-playwright (1, 2) (push) Failing after 12s
Playwright Tests / test-playwright (2, 2) (push) Failing after 9s
pre-commit / pre-commit (push) Failing after 1m56s
Test Backend / test-backend (push) Failing after 2m50s
Compose Smoke Test / test-compose (push) Failing after 9s
Playwright Tests / merge-reports (push) Failing after 2m16s
Docs / docs (push) Successful in 19s
Playwright Tests / test-playwright (1, 2) (push) Failing after 12s
Playwright Tests / test-playwright (2, 2) (push) Failing after 9s
pre-commit / pre-commit (push) Failing after 1m56s
Test Backend / test-backend (push) Failing after 2m50s
Compose Smoke Test / test-compose (push) Failing after 9s
Playwright Tests / merge-reports (push) Failing after 2m16s
A worker over FLOW_WORKER_MAX_RSS_MB (--worker-max-rss) when its node returns is retired instead of kept warm, in the shared pool and in every declared one; 0, the default, keeps them all. The engine now accounts its own memory (FLOW_RAM / --ram, else the box or the cgroup limit less 1 GB), so a node's `ram` declaration holds locally. A declared environment the shared pool already has is that pool rather than a duplicate set of warm workers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from fluksio.flow.resources import (
|
||||
derive_env,
|
||||
fair_share_env,
|
||||
machine_gpus,
|
||||
machine_ram_mb,
|
||||
)
|
||||
from fluksio.flow.schemas import NodeDef, Resources
|
||||
|
||||
@@ -44,6 +45,20 @@ def test_the_cards_are_counted_from_the_devices_the_driver_makes(monkeypatch):
|
||||
assert machine_gpus() == 0
|
||||
|
||||
|
||||
def test_memory_is_the_containers_limit_where_that_is_lower(monkeypatch, tmp_path):
|
||||
"""The host's total is memory a container would be killed for using."""
|
||||
from fluksio.flow import resources
|
||||
|
||||
box = os.sysconf("SC_PHYS_PAGES") * os.sysconf("SC_PAGE_SIZE") // 2**20
|
||||
limit = tmp_path / "memory.max"
|
||||
monkeypatch.setattr(resources, "CGROUP_MEMORY_MAX", limit)
|
||||
|
||||
limit.write_text("max\n")
|
||||
assert machine_ram_mb() == box - resources.ENGINE_RAM_RESERVE_MB
|
||||
limit.write_text(f"{2048 * 2**20}\n")
|
||||
assert machine_ram_mb() == 2048 - resources.ENGINE_RAM_RESERVE_MB
|
||||
|
||||
|
||||
def test_what_is_free_is_what_was_handed_out():
|
||||
accountant = ResourceAccountant(cpus=4, gpus=0)
|
||||
held = accountant.try_take(cpus=3, gpus=0)
|
||||
|
||||
@@ -659,6 +659,9 @@ def test_a_declared_environment_gets_a_pool_of_its_own(pool):
|
||||
assert pool.for_env({"OMP_NUM_THREADS": "3"}) is not child
|
||||
# Nothing to derive means the shared pool, with no second process anywhere.
|
||||
assert pool.for_env({}) is pool
|
||||
# Nor does deriving what the shared pool's workers were started with.
|
||||
pool.env["OMP_NUM_THREADS"] = "4"
|
||||
assert pool.for_env({"OMP_NUM_THREADS": "4"}) is pool
|
||||
|
||||
|
||||
def test_a_child_worker_is_started_with_what_it_was_given(pool):
|
||||
@@ -698,6 +701,23 @@ def test_retiring_the_cards_leaves_the_other_pools_warm(pool):
|
||||
assert threads._generation == before[1]
|
||||
|
||||
|
||||
def test_a_worker_over_the_memory_ceiling_is_not_kept_warm(pool):
|
||||
"""A library keeps what it allocated, so the process is what gives it back."""
|
||||
code = "import os\n\n\ndef process():\n return {'pid': os.getpid()}\n"
|
||||
# Any interpreter is past a megabyte, so every call retires its worker.
|
||||
tight = PythonWorkerPool(python=sys.executable, size=1, max_rss_mb=1)
|
||||
tight.start()
|
||||
try:
|
||||
first = tight.run("demo", "pid", code, {}, "demo.pid", timeout=10)
|
||||
assert tight.run("demo", "pid", code, {}, "demo.pid", timeout=10) != first
|
||||
# The pools a declaration gets are held to the same ceiling.
|
||||
assert tight.for_env({"OMP_NUM_THREADS": "2"}).max_rss_mb == 1
|
||||
finally:
|
||||
tight.stop()
|
||||
# Without one, the same slot answers twice from the same process.
|
||||
assert run(pool, code, node="pid") == run(pool, code, node="pid")
|
||||
|
||||
|
||||
def test_cancelling_reaches_a_node_running_in_a_child(pool):
|
||||
child = pool.for_env({"OMP_NUM_THREADS": "2"})
|
||||
started = threading.Event()
|
||||
|
||||
Reference in New Issue
Block a user