build-shared.sh: rebuild on stale objects, skip needless re-archiving, link atomically
Three correctness fixes to the --shared build path: - Only skipped rebuilding a .pic.o when it existed at all, never checking whether the source .cpp was newer - confirmed live: Tag.pic.o (and 6 other JetCore objects) sat uncompiled since 2026-08-25 while Tag.cpp itself picked up same-day fixes, so every .jtc silently ran yesterday's JetCore code. - Only re-archive libCoreUtils_pic.a when a member object actually changed, instead of unconditionally on every build. - Link to a temp file in the same directory, then rename() it into place, instead of linking directly onto the live .jtc path. A running APIServer may already have that exact path dlopen()ed (hot-reload watches this directory) - overwriting it in place corrupts the live mapping's code pages out from under the process, causing a dlclose() SIGSEGV the next time it reloads. rename() never touches the old inode, so anything with it still mapped keeps seeing the unmodified original.
This commit is contained in:
1 parent
7893746582
commit
54b6e522f9
1 file changed
+34
-3
+34
-3
@@ -37,12 +37,20 @@ echo "Compiling $INPUT -> $STEM.compiled.cpp (shared mode)"
|
|||||||
"$HERE/jetc" --shared "$INPUT" "$HERE/$STEM.compiled.cpp"
|
"$HERE/jetc" --shared "$INPUT" "$HERE/$STEM.compiled.cpp"
|
||||||
|
|
||||||
echo "Building JetCore runtime objects as position-independent code..."
|
echo "Building JetCore runtime objects as position-independent code..."
|
||||||
|
# Rebuild whenever the SOURCE is newer than the cached object, not just
|
||||||
|
# when the object is missing - a pure existence check silently keeps
|
||||||
|
# serving a stale compile forever once the .o exists once, even after the
|
||||||
|
# .cpp changes. Confirmed this bit for real: Tag.pic.o (and 6 other
|
||||||
|
# JetCore objects) sat uncompiled since 2026-08-25 while Tag.cpp itself
|
||||||
|
# picked up same-day fixes (the <try> tag dispatch among them) - every
|
||||||
|
# .jtc built off the stale cache silently ran yesterday's JetCore code,
|
||||||
|
# with <try> falling through as unrecognized literal text at runtime.
|
||||||
list=""
|
list=""
|
||||||
for f in "$JC"/*.cpp; do
|
for f in "$JC"/*.cpp; do
|
||||||
base=$(basename "$f")
|
base=$(basename "$f")
|
||||||
if [ "$base" = "jet-2.0.cpp" ]; then continue; fi
|
if [ "$base" = "jet-2.0.cpp" ]; then continue; fi
|
||||||
obj="$HERE/${base%.cpp}.pic.o"
|
obj="$HERE/${base%.cpp}.pic.o"
|
||||||
if [ ! -f "$obj" ]; then
|
if [ ! -f "$obj" ] || [ "$f" -nt "$obj" ]; then
|
||||||
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$f" -o "$obj" &
|
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$f" -o "$obj" &
|
||||||
fi
|
fi
|
||||||
list="$list $obj"
|
list="$list $obj"
|
||||||
@@ -62,21 +70,44 @@ done
|
|||||||
# at dlopen() time, even though nothing here actually uses it - a normal
|
# at dlopen() time, even though nothing here actually uses it - a normal
|
||||||
# archive link never pulls IMFMessage.o in at all, so it never hits this.
|
# archive link never pulls IMFMessage.o in at all, so it never hits this.
|
||||||
cu_pic_list=""
|
cu_pic_list=""
|
||||||
|
cu_rebuilt=false
|
||||||
for f in "$CU"/*.cpp; do
|
for f in "$CU"/*.cpp; do
|
||||||
base=$(basename "$f")
|
base=$(basename "$f")
|
||||||
obj="$HERE/cu_${base%.cpp}.pic.o"
|
obj="$HERE/cu_${base%.cpp}.pic.o"
|
||||||
if [ ! -f "$obj" ]; then
|
if [ ! -f "$obj" ] || [ "$f" -nt "$obj" ]; then
|
||||||
g++ -g -fPIC -c -std=c++23 -I"$CU" "$f" -o "$obj" &
|
g++ -g -fPIC -c -std=c++23 -I"$CU" "$f" -o "$obj" &
|
||||||
|
cu_rebuilt=true
|
||||||
fi
|
fi
|
||||||
cu_pic_list="$cu_pic_list $obj"
|
cu_pic_list="$cu_pic_list $obj"
|
||||||
done
|
done
|
||||||
wait
|
wait
|
||||||
|
# Only re-archive when something actually changed - ar rcs on an unchanged
|
||||||
|
# member set is harmless but pointless work every single build otherwise.
|
||||||
|
if [ "$cu_rebuilt" = true ] || [ ! -f "$HERE/libCoreUtils_pic.a" ]; then
|
||||||
ar rcs "$HERE/libCoreUtils_pic.a" $cu_pic_list
|
ar rcs "$HERE/libCoreUtils_pic.a" $cu_pic_list
|
||||||
|
fi
|
||||||
|
|
||||||
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$HERE/$STEM.compiled.cpp" -o "$HERE/$STEM.compiled.pic.o"
|
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$HERE/$STEM.compiled.cpp" -o "$HERE/$STEM.compiled.pic.o"
|
||||||
|
|
||||||
echo "Linking $OUTPUT..."
|
echo "Linking $OUTPUT..."
|
||||||
g++ -shared -fPIC -o "$OUTPUT" "$HERE/$STEM.compiled.pic.o" $list -std=c++23 \
|
# Link to a temp file in the SAME directory as $OUTPUT, then rename() it
|
||||||
|
# into place, rather than linking directly onto $OUTPUT - a live
|
||||||
|
# APIServer instance may already have this exact path dlopen()ed (hot-
|
||||||
|
# reload watches this directory). g++/ld opens $OUTPUT with O_TRUNC and
|
||||||
|
# writes the new object into that same inode, which is also the one
|
||||||
|
# backing the running server's mmap()ed view of the OLD module -
|
||||||
|
# corrupting its code pages out from under it mid-flight. Confirmed as
|
||||||
|
# the real cause of a live, reproducible dlclose() SIGSEGV (garbage PC
|
||||||
|
# inside _dl_call_fini): the server's own unload-before-reload path
|
||||||
|
# calls dlclose() on the now-corrupted mapping shortly after a build
|
||||||
|
# like this one lands. rename() is atomic and, same filesystem, never
|
||||||
|
# touches the old inode - any process with it still mapped keeps seeing
|
||||||
|
# the unmodified original until it unmaps, and the watcher's own
|
||||||
|
# dlclose()+dlopen() cycle (triggered by the CLOSE_WRITE this rename
|
||||||
|
# still generates) picks up the new file cleanly on its next load.
|
||||||
|
TMPOUT="$(dirname "$OUTPUT")/.$STEM.jtc.tmp.$$"
|
||||||
|
g++ -shared -fPIC -o "$TMPOUT" "$HERE/$STEM.compiled.pic.o" $list -std=c++23 \
|
||||||
-L"$HERE" -lCoreUtils_pic -lmysqlclient -lcrypto
|
-L"$HERE" -lCoreUtils_pic -lmysqlclient -lcrypto
|
||||||
|
mv -f "$TMPOUT" "$OUTPUT"
|
||||||
|
|
||||||
echo "Built $OUTPUT"
|
echo "Built $OUTPUT"
|
||||||
Reference in new issue
Block a user