Skip to content

Commit 2bdbe00

Browse files
authored
Merge pull request #125 from DeterminateSystems/multithreaded-eval-v2
Multithreaded evaluator
2 parents 088770b + 4b34658 commit 2bdbe00

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1809
-898
lines changed

.github/workflows/build.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ jobs:
129129
nix_config:
130130
- "lazy-trees = true"
131131
- "lazy-trees = false"
132+
- "eval-cores = 24"
132133
glob:
133134
- "[0-d]*"
134135
- "[e-l]*"
@@ -151,23 +152,34 @@ jobs:
151152
path: flake-regressions/tests
152153
- uses: DeterminateSystems/determinate-nix-action@main
153154
- uses: DeterminateSystems/flakehub-cache-action@main
154-
- env:
155-
PARALLEL: "-P 50%"
155+
- name: Run flake regression tests
156+
env:
157+
#PARALLEL: ${{ !contains(matrix.nix_config, 'eval-cores') && '-P 50%' || '-P 1' }}
158+
PARALLEL: '-P 1'
156159
FLAKE_REGRESSION_GLOB: ${{ matrix.glob }}
157160
NIX_CONFIG: ${{ matrix.nix_config }}
161+
PREFETCH: "1"
158162
run: |
159163
set -x
164+
echo "PARALLEL: $PARALLEL"
165+
echo "NIX_CONFIG: $NIX_CONFIG"
160166
if [ ! -z "${NSC_CACHE_PATH:-}" ]; then
161167
mkdir -p "${NSC_CACHE_PATH}/nix/xdg-cache"
162168
export XDG_CACHE_HOME="${NSC_CACHE_PATH}/nix/xdg-cache"
163169
fi
164170
nix build -L --out-link ./new-nix
165171
export PATH=$(pwd)/new-nix/bin:$PATH
172+
[[ $(type -p nix) = $(pwd)/new-nix/bin/nix ]]
173+
174+
nix config show lazy-trees
175+
nix config show eval-cores
176+
lscpu
177+
nproc
166178
167179
if ! flake-regressions/eval-all.sh; then
168180
echo "Some failed, trying again"
169181
printf "\n\n\n\n\n\n\n\n"
170-
flake-regressions/eval-all.sh
182+
NIX_REMOTE=/tmp/nix flake-regressions/eval-all.sh
171183
fi
172184
173185
manual:

packaging/dependencies.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ scope: {
8585
"--with-coroutine"
8686
"--with-iostreams"
8787
"--with-url"
88+
"--with-thread"
8889
];
8990
enableIcu = false;
9091
}).overrideAttrs

src/libexpr-c/nix_api_value.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ ValueType nix_get_type(nix_c_context * context, const nix_value * value)
175175
switch (v.type()) {
176176
case nThunk:
177177
return NIX_TYPE_THUNK;
178+
case nFailed:
179+
return NIX_TYPE_FAILED;
178180
case nInt:
179181
return NIX_TYPE_INT;
180182
case nFloat:

src/libexpr-c/nix_api_value.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ typedef enum {
3232
NIX_TYPE_ATTRS,
3333
NIX_TYPE_LIST,
3434
NIX_TYPE_FUNCTION,
35-
NIX_TYPE_EXTERNAL
35+
NIX_TYPE_EXTERNAL,
36+
NIX_TYPE_FAILED,
3637
} ValueType;
3738

3839
// forward declarations

src/libexpr-tests/value/print.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ using namespace testing;
1010
struct ValuePrintingTests : LibExprTest
1111
{
1212
template<class... A>
13-
void test(Value v, std::string_view expected, A... args)
13+
void test(Value & v, std::string_view expected, A... args)
1414
{
1515
std::stringstream out;
1616
v.print(state, out, args...);
@@ -625,10 +625,11 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
625625
vThree.mkInt(3);
626626

627627
builder.insert(state.symbols.create("three"), &vThree);
628-
vAttrs.mkAttrs(builder.finish());
628+
Value vAttrs2;
629+
vAttrs2.mkAttrs(builder.finish());
629630

630631
test(
631-
vAttrs,
632+
vAttrs2,
632633
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }",
633634
PrintOptions{.ansiColors = true, .maxAttrs = 1});
634635
}

src/libexpr-tests/value/value.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ TEST_F(ValueTest, unsetValue)
1111
{
1212
Value unsetValue;
1313
ASSERT_EQ(false, unsetValue.isValid());
14-
ASSERT_EQ(nThunk, unsetValue.type(true));
1514
ASSERT_DEATH(unsetValue.type(), "");
1615
}
1716

src/libexpr/eval-gc.cc

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,88 @@ static void * oomHandler(size_t requested)
3333
throw std::bad_alloc();
3434
}
3535

36+
static size_t getFreeMem()
37+
{
38+
/* On Linux, use the `MemAvailable` or `MemFree` fields from
39+
/proc/cpuinfo. */
40+
# ifdef __linux__
41+
{
42+
std::unordered_map<std::string, std::string> fields;
43+
for (auto & line :
44+
tokenizeString<std::vector<std::string>>(readFile(std::filesystem::path("/proc/meminfo")), "\n")) {
45+
auto colon = line.find(':');
46+
if (colon == line.npos)
47+
continue;
48+
fields.emplace(line.substr(0, colon), trim(line.substr(colon + 1)));
49+
}
50+
51+
auto i = fields.find("MemAvailable");
52+
if (i == fields.end())
53+
i = fields.find("MemFree");
54+
if (i != fields.end()) {
55+
auto kb = tokenizeString<std::vector<std::string>>(i->second, " ");
56+
if (kb.size() == 2 && kb[1] == "kB")
57+
return string2Int<size_t>(kb[0]).value_or(0) * 1024;
58+
}
59+
}
60+
# endif
61+
62+
/* On non-Linux systems, conservatively assume that 25% of memory is free. */
63+
long pageSize = sysconf(_SC_PAGESIZE);
64+
long pages = sysconf(_SC_PHYS_PAGES);
65+
if (pageSize > 0 && pages > 0)
66+
return (static_cast<size_t>(pageSize) * static_cast<size_t>(pages)) / 4;
67+
return 0;
68+
}
69+
70+
/**
71+
* When a thread goes into a coroutine, we lose its original sp until
72+
* control flow returns to the thread. This causes Boehm GC to crash
73+
* since it will scan memory between the coroutine's sp and the
74+
* original stack base of the thread. Therefore, we detect when the
75+
* current sp is outside of the original thread stack and push the
76+
* entire thread stack instead, as an approximation.
77+
*
78+
* This is not optimal, because it causes the stack below sp to be
79+
* scanned. However, we usually we don't have active coroutines during
80+
* evaluation, so this is acceptable.
81+
*
82+
* Note that we don't scan coroutine stacks. It's currently assumed
83+
* that we don't have GC roots in coroutines.
84+
*/
85+
void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
86+
{
87+
void *& sp = *sp_ptr;
88+
auto pthread_id = reinterpret_cast<pthread_t>(_pthread_id);
89+
size_t osStackSize;
90+
char * osStackHi;
91+
char * osStackLo;
92+
93+
# ifdef __APPLE__
94+
osStackSize = pthread_get_stacksize_np(pthread_id);
95+
osStackHi = (char *) pthread_get_stackaddr_np(pthread_id);
96+
osStackLo = osStackHi - osStackSize;
97+
# else
98+
pthread_attr_t pattr;
99+
if (pthread_attr_init(&pattr))
100+
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
101+
# ifdef HAVE_PTHREAD_GETATTR_NP
102+
if (pthread_getattr_np(pthread_id, &pattr))
103+
throw Error("fixupBoehmStackPointer: pthread_getattr_np failed");
104+
# else
105+
# error "Need `pthread_attr_get_np`"
106+
# endif
107+
if (pthread_attr_getstack(&pattr, (void **) &osStackLo, &osStackSize))
108+
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
109+
if (pthread_attr_destroy(&pattr))
110+
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
111+
osStackHi = osStackLo + osStackSize;
112+
# endif
113+
114+
if (sp >= osStackHi || sp < osStackLo) // sp is outside the os stack
115+
sp = osStackLo;
116+
}
117+
36118
static inline void initGCReal()
37119
{
38120
/* Initialise the Boehm garbage collector. */
@@ -63,8 +145,11 @@ static inline void initGCReal()
63145

64146
GC_set_oom_fn(oomHandler);
65147

66-
/* Set the initial heap size to something fairly big (25% of
67-
physical RAM, up to a maximum of 384 MiB) so that in most cases
148+
GC_set_sp_corrector(&fixupBoehmStackPointer);
149+
assert(GC_get_sp_corrector());
150+
151+
/* Set the initial heap size to something fairly big (80% of
152+
free RAM, up to a maximum of 4 GiB) so that in most cases
68153
we don't need to garbage collect at all. (Collection has a
69154
fairly significant overhead.) The heap size can be overridden
70155
through libgc's GC_INITIAL_HEAP_SIZE environment variable. We
@@ -75,15 +160,10 @@ static inline void initGCReal()
75160
if (!getEnv("GC_INITIAL_HEAP_SIZE")) {
76161
size_t size = 32 * 1024 * 1024;
77162
# if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
78-
size_t maxSize = 384 * 1024 * 1024;
79-
long pageSize = sysconf(_SC_PAGESIZE);
80-
long pages = sysconf(_SC_PHYS_PAGES);
81-
if (pageSize != -1)
82-
size = (pageSize * pages) / 4; // 25% of RAM
83-
if (size > maxSize)
84-
size = maxSize;
163+
size_t maxSize = 4ULL * 1024 * 1024 * 1024;
164+
auto free = getFreeMem();
165+
size = std::max(size, std::min((size_t) (free * 0.5), maxSize));
85166
# endif
86-
debug("setting initial heap size to %1% bytes", size);
87167
GC_expand_hp(size);
88168
}
89169
}

0 commit comments

Comments
 (0)