Skip to content

Commit fde1e72

Browse files
committed
Add memory_limit functions
1 parent b949759 commit fde1e72

File tree

5 files changed

+108
-1
lines changed

5 files changed

+108
-1
lines changed

docs/src/index.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ The elastic cluster manager automatically adds new workers to an automatically c
1414
Since workers can appear and disappear dynamically, initializing them (loading packages, etc.) via the standard `Distributed.@everywhere` macro is problematic, as workers added afterwards won't be initialized. Parallel processing tools provides the macro [`@always_everywhere`](@ref) to run code globally on all current processes, but also store the code so it can be run again on future new worker processes. Workers that are part of a [`FlexWorkerPool`](@ref) will be updated automatically on `take!` and `onworker`. You can also use [`ensure_procinit`](@ref) to manually update all workers
1515
to all `@always_everywhere` used so far.
1616

17-
The function [`pinthreads_auto`](@ref) (used inside of `@always_everywhere`) provides a convenient way to perform some automatic thread pinning on all processes. Note that it needs to follow an [`import ThreadPinning`](https://github.com/carstenbauer/ThreadPinning.jl/), and that more complex use cased may require customized thread pinning for best performance.
17+
The function [`pinthreads_auto`](@ref) (used inside of `@always_everywhere`) provides a convenient way to perform some automatic thread pinning on all processes. Note that it needs to follow an [`import ThreadPinning`](https://github.com/carstenbauer/ThreadPinning.jl/), and that more complex use cases may require customized thread pinning for best performance.
18+
19+
Some batch system configurations can result in whole Julia processes, or even a whole batch job, being terminated if a process exceeds its memory limit. In such cases, you can try to gain a softer failure mode by setting a custom (slightly smaller) memory limit using [`memory_limit!`](@ref).
1820

1921
For example:
2022

@@ -30,6 +32,9 @@ using ParallelProcessingTools, Distributed
3032

3133
import ThreadPinning
3234
pinthreads_auto()
35+
36+
# Optional: Set a custom memory limit for worker processes:
37+
# myid() != 1 && memory_limit!(8 * 1000^3) # 8 GB
3338
end
3439

3540
runmode = OnLocalhost(n = 4)

src/ParallelProcessingTools.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ using Parameters: @with_kw
2525
include("custom_cluster_managers.jl")
2626
using .CustomClusterManagers: ElasticManager
2727

28+
include("memory.jl")
2829
include("display.jl")
2930
include("waiting.jl")
3031
include("exceptions.jl")

src/memory.jl

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This file is a part of ParallelProcessingTools.jl, licensed under the MIT License (MIT).
2+
3+
const _RLIMIT_AS = 9 # maximum size of the process's virtual memory
4+
5+
const _EINVAL = 22 # libc errno for "Invalid argument"
6+
7+
# Struct for libc getrlimit and setrlimit:
8+
mutable struct _RLimit
9+
cur::Clong
10+
max::Clong
11+
end
12+
13+
14+
"""
15+
memory_limit()
16+
17+
Gets the virtual memory limit for the current Julia process.
18+
19+
Returns a tuple `(soft_limit::Int64, hard_limit::Int64)` (in units of bytes).
20+
Values of `-1` mean unlimited.
21+
22+
!!! note
23+
Currently only works on Linux, simply returns `(Int64(-1), Int64(-1))` on
24+
other operationg systems.
25+
"""
26+
function memory_limit end
27+
export memory_limit
28+
29+
@static if Sys.islinux()
30+
function memory_limit()
31+
rlim = Ref(_RLimit(0, 0))
32+
rc = ccall(:getrlimit, Cint, (Cint, Ref{_RLimit}), _RLIMIT_AS, rlim)
33+
if rc != 0
34+
error("Failed to get memory limits: ", Base.Libc.strerror(Base.Libc.errno()))
35+
end
36+
return rlim[].cur, rlim[].max
37+
end
38+
else
39+
memory_limit() = Int64(-1), Int64(-1)
40+
end
41+
42+
43+
"""
44+
memory_limit!(soft_limit::Integer, hard_limit::Integer = -1)
45+
46+
Sets the virtual memory limit for the current Julia process.
47+
48+
`soft_limit` and `hard_limit` are in units of bytes. Values of `-1` mean
49+
unlimited. `hard_limit` must not be stricter than `soft_limit`, and should
50+
typically be set to `-1`.
51+
52+
Returns `(soft_limit::Int64, hard_limit::Int64)`.
53+
54+
!!! note
55+
Currently only has an effect on Linux, does nothing and simply returns
56+
`(Int64(-1), Int64(-1))` on other operating systems.
57+
"""
58+
function memory_limit! end
59+
export memory_limit!
60+
61+
@static if Sys.islinux()
62+
function memory_limit!(soft_limit::Integer, hard_limit::Integer = Int64(-1))
63+
GC.gc()
64+
65+
rlim = Ref(_RLimit(soft_limit, hard_limit))
66+
rc = ccall(:setrlimit, Cint, (Cint, Ref{_RLimit}), _RLIMIT_AS, rlim)
67+
if rc != 0
68+
errno = Base.Libc.errno()
69+
if errno == _EINVAL
70+
throw(ArgumentError("Invalid soft/hard memory limit $soft_limit/$hard_limit."))
71+
else
72+
error("Failed to set memory limit: ", errno, Base.Libc.strerror(errno))
73+
end
74+
end
75+
return rlim[].cur, rlim[].max
76+
end
77+
else
78+
memory_limit!(soft_limit::Integer, hard_limit::Integer) = Int64(-1), Int64(-1)
79+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Test.@testset "Package ParallelProcessingTools" begin
99
@info "Testing with $(Base.Threads.nthreads()) Julia threads."
1010

1111
include("test_aqua.jl")
12+
include("test_memory.jl")
1213
include("test_waiting.jl")
1314
include("test_states.jl")
1415
include("test_fileio.jl")

test/test_memory.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# This file is a part of ParallelProcessingTools.jl, licensed under the MIT License (MIT).
2+
3+
using Test
4+
using ParallelProcessingTools
5+
6+
@testset "memory" begin
7+
@testset "memory_limit" begin
8+
@test @inferred(memory_limit()) isa Tuple{<:Integer,<:Integer}
9+
limit = Int(min(typemax(Int), 8 * Int64(1024)^3))
10+
new_limits = (limit, -1)
11+
if Sys.islinux()
12+
@test @inferred(memory_limit!(new_limits...)) == new_limits
13+
@test @inferred(memory_limit()) == new_limits
14+
stricter_limit = round(Int, 0.9*limit)
15+
@test_throws ArgumentError @inferred(memory_limit!(limit, stricter_limit))
16+
else
17+
@test @inferred(memory_limit!(new_limits...)) == (-1, -1)
18+
@test @inferred(memory_limit()) == (-1, -1)
19+
end
20+
end
21+
end

0 commit comments

Comments
 (0)