mirror of
https://github.com/Tencent/flare.git
synced 2026-10-17 04:41:22 +08:00
86f175c3c4
* feat(macos): build flare on macOS (darwin/arm64) + thirdparty alignment & bug fixes
Make flare build with both blade and bazel on macOS (Apple Silicon,
darwin/arm64); previously Linux-only. Verified: `blade build flare/...` and
`bazel build //flare/...` (653 targets) both green on macOS arm64.
Third-party / build system:
- Align bazel deps with the verified blade build: glog 0.4.0->0.7.1,
zlib 1.2.13->1.3.2, openssl 1.0.2u->1.1.1w (1.1 required by HMAC_CTX usage).
- glog: extend the export-guard patch to every header gating <glog/export.h>
behind GLOG_USE_GLOG_EXPORT, and update CHECK_OP to use
google::logging::internal::{Make,}CheckOpString.
- foreign_cc macOS fixes: force a real `ar` (rules_foreign_cc defaults AR to
Apple's libtool, incompatible with autotools' ar-style calls); gate -lrt to
Linux; pick .dylib vs .so; build nghttp2 static-only (its sandbox dylib
install_name breaks curl's configure run-time check).
- Register the host preinstalled make toolchain (rules_foreign_cc's bundled
make segfaults building jemalloc on macOS; blade also uses the system make).
- Track patch files as unpack-rule inputs (foreign_build.bld).
- Silence third-party warnings (external/ and in-repo thirdparty/) via
per_file_copt.
flare portability:
- IO: new Poller abstraction with epoll (Linux) and kqueue (macOS) backends;
event_loop/descriptor rewired off raw epoll; eventfd -> pipe notifier.
- net/thread/cpu/rlimit platform guards; OpenSSL 1.1 HMAC_CTX API.
- Add missing `gflags/gflags.h` / `<unistd.h>` includes (were pulled in
transitively by the old glog).
- gperftools/tcmalloc is Linux-only: excluded on darwin (mirrors blade);
macOS uses jemalloc.
Bug fixes (also affect Linux):
- base/expected.h: Unexpected::swap referenced a nonexistent member
(other.unex -> other.unex_); never compiled once instantiated.
- io/detail/writing_buffer_list.cc: FlushTo crashed (CHECK) on a benign drain
race (head_ stale while tail_ == nullptr); now treats the list as empty.
- base/chrono.{h,cc}: clocks delegated to a hand-rolled clock_gettime that
mixed clock epochs (broke TimerWorker::wait_until); now use
std::chrono::*::now(); ReadCoarseSystemClock duration_casts to the clock's
duration.
- fiber/detail/scheduling_group.cc: spin-wait bound was a hardcoded TSC-tick
count assuming a GHz-scale TSC; now derived from the measured TSC frequency.
- base/internal/dpc.cc: "Run N DPCs" logged cbs.size() (always 0 at loop exit)
instead of the accumulated dpcs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fiber: defeat Clang's TLS-base CSE across returns_twice on AArch64
On AArch64, Clang caches the resolved thread_local slot address in a
callee-saved register and reuses it across `[[gnu::returns_twice]]`
calls. `jump_context` may resume the same C++ frame on a different OS
thread, so the cached slot points to the previous worker's TLS region
on resume, and accesses go to the wrong memory.
GCC handles `returns_twice` more conservatively (it re-issues
`mrs tpidr_el0` after the call), which is why Linux flare worked
inlined for years -- Linux + GCC, not "Linux".
Centralize the canonical `jump_context` declaration in a new
`context.h` (so every TU sees the `returns_twice` attribute) and
force the three TLS accessors out-of-line: `[[gnu::noinline]]` makes
each access an ABI call, and ABI rules force the caller to redo the
TLS resolution -- Clang can't CSE across a real function boundary.
Discovered while porting flare to macOS arm64 (Apple Clang), but the
underlying hazard exists on any Clang aarch64 build; GCC users were
just lucky.
* io/kqueue: fix Modify to disable removed filters; stop fanning EV_EOF to opposite direction
Two macOS-port correctness fixes in the kqueue poller, both surfaced
by a flare RPC client that died with STATUS_IO_ERROR on the first
write to a freshly-opened connection.
1. `Modify` now explicitly EV_DISABLEs filters that aren't in the
new event mask. Unlike Linux epoll (EPOLL_CTL_MOD replaces the
whole event bitmask in one shot), kqueue registers EVFILT_READ
and EVFILT_WRITE independently -- a bare EV_ADD on one filter
does NOT affect the other. Without explicit disable, the kernel
keeps firing the "removed" filter under level-triggered mode,
and `OnWritable()` runs on an empty writing buffer, which the
connection layer mistakes for "remote closed" and kills the
connection. EV_DISABLE (not EV_DELETE) is what we want -- it
pauses delivery while preserving the filter's internal readiness
state, mirroring epoll's behaviour. (EV_DELETE + later EV_ADD on
a socket that is already readable produces no edge with EV_CLEAR,
and the reader stalls forever.)
2. `Wait` no longer fans an EV_EOF flag out into both kPollerRead
and kPollerWrite. EV_EOF on EVFILT_READ just means the peer
half-closed for reading; the write direction is still usable
until the next write returns EPIPE. The old fan-out manufactured
a fake write event whenever the read side EOF'd, kicking
`OnWritable()` on an empty writing buffer -- same false-disconnect
chain as #1.
* io/util/socket: SetCloseOnExec must use F_SETFD; rename helper to SetStatusFlags
FD_CLOEXEC is a *file-descriptor* flag (F_GETFD / F_SETFD), not a
file-status flag (F_GETFL / F_SETFL). The existing `SetCloseOnExec`
routed through the generic `SetFlags` helper, which goes through
F_SETFL, and so passed FD_CLOEXEC = 0x01 into the status-flag space.
On Linux glibc, the unsupported bit is silently discarded by F_SETFL,
so this was invisible. On Darwin the kernel actually interprets the
0x01 bit inside the F_SETFL flag space: O_NONBLOCK gets wiped off the
listening socket and O_APPEND gets set. The downstream effect is that
`accept(2)` on the listening socket reverts to blocking and the
server's accept loop hangs forever after the first connection.
Fix `SetCloseOnExec` to use F_GETFD / F_SETFD directly, and rename
the generic helper from `SetFlags` to `SetStatusFlags` so the
descriptor-flag-vs-status-flag distinction is hard to miss next time.
The only remaining caller of the helper is `SetNonBlocking`, which
is correct (O_NONBLOCK *is* a status flag).
* io/native: treat ENOTCONN as transient in FlushWritingBuffer
When a non-blocking TCP socket is in the middle of its `connect(2)`,
Linux returns `EAGAIN` on `write(2)`/`sendmsg(2)`, which flare maps
to `FlushStatus::SystemBufferSaturated` and retries via the write
event. Darwin instead returns `ENOTCONN` ("Socket is not connected")
in the same situation -- the kernel distinguishes "would block on a
connected socket" (EAGAIN) from "not yet connected" (ENOTCONN).
The existing code treated ENOTCONN as a fatal write error, returned
`NothingWritten`, and `Kill(CleanupReason::Disconnect)` immediately
fired. The first RPC on every channel died with STATUS_IO_ERROR.
Fold ENOTCONN into the EAGAIN / EWOULDBLOCK branch so the write is
deferred until the kernel signals writability (which happens when
the connect completes), matching Linux semantics.
* base/write_mostly: order members so mutex outlives TLS wrappers
`WriteMostly` had `tls_buffer_` declared before `mutex`, which means
`mutex` was destroyed *first* (members are destroyed in reverse
declaration order). When `tls_buffer_` was then torn down, each
per-thread `WriteBufferWrapper` ran its destructor and tried to
`std::unique_lock(parent_->mutex)` -- locking an already-destroyed
mutex.
libstdc++ on Linux silently allowed this (the underlying pthread
mutex didn't actually reject), so the bug was invisible.
libc++ on Darwin returns EINVAL from `pthread_mutex_lock`, std::mutex
throws `system_error("mutex lock failed: Invalid argument")`, and
since destructors are noexcept by default the unhandled exception
aborts via std::terminate.
Reorder the members so `tls_buffer_` is the last declared field
(thus the first destroyed): both `mutex` and `exited_thread_combined_`
are still alive while the wrappers merge into them.
* base/deferred_test: initialise bool to avoid UB read
`Deferred.All` declared `bool f4;` and then `ASSERT_FALSE(f4)`
before any store. Reading an uninitialised bool is undefined
behaviour: the value is "indeterminate" and a load of an
indeterminate value is itself UB regardless of which bit pattern
happens to be in the stack slot.
Linux happened to observe zero on the stack and slipped through.
macOS Clang traps on the indeterminate read (SIGTRAP, exit 133),
exposing the bug.
Initialise `bool f4 = false`.
* macOS port: platform-specific test adjustments
A bag of test-only changes that fall out of the macOS port.
* attribute_test.cc -- SetCurrentAffinity skipped on Darwin.
Darwin has no pthread_setaffinity_np equivalent and the legacy
thread_policy_set THREAD_AFFINITY_POLICY is hint-only; the
`expected == GetCurrentProcessorId()` assertion cannot hold.
* descriptor_test.cc -- ConcurrentRestartRead skipped on Darwin.
The test attaches a PipeDesc with Event::Write to the read end
of a closed-write pipe. Linux epoll never fires EPOLLOUT on
that fd; macOS kqueue fires EVFILT_WRITE (no write buffer ==
"has space"). The fire trips a pre-existing race in
`Descriptor::QueueCleanupCallbackCheck`: FireXxxEvent does not
consult `cleanup_pending_` before fetch_add, so an event between
cleanup-task queueing and execution makes the strict CHECK at
descriptor.cc abort. Fixing that race needs Dekker-style
ordering between FireXxxEvent and Kill -- bigger scope than
this batch.
* writing_datagram_list_test.cc -- shrink the test datagram from
16384 to 4096 bytes. Darwin's `net.inet.udp.maxdgram` defaults
to 9216, and sendmsg() rejects larger UDP payloads with
EMSGSIZE; Linux is happy up to ~64KB.
* datagram_transceiver_test.cc -- cap the fragment count from
60'000 to 8192 on Darwin. Still well above IOV_MAX (1024 on
both libc's) so the "flatten on iovec overflow" path is still
exercised; total payload now fits under net.inet.udp.maxdgram.
* uri_matcher_test.cc -- regex pattern `"\\abc.*efg"` used the
`\a` escape, which is not a valid ECMAScript escape. libstdc++
accepted it silently (treating it as literal `a`); libc++ on
Darwin throws regex_error("invalid escaped character") and the
test aborts. The intended pattern was `abc.*efg`.
* dirty_hook_test.cc, hooking_mock_test.cc -- skipped on Darwin.
Both rely on `mprotect`-ing the text segment to writable to
overwrite a function prologue. macOS arm64 enforces W^X on
signed code pages: mprotect returns EPERM, the CHECK aborts.
Toggling JIT-style write-execute would require
`pthread_jit_write_protect_np`, the
`com.apple.security.cs.allow-jit` entitlement, and code placed
in a JIT region -- none of which apply to the regular text
segment that DirtyHook patches. (The single non-hook subtest
in hooking_mock_test, `NotEnabled`, still runs.)
* fixup: satisfy clang-format-15 (LLVM style) on the macOS port commits
CI's clang-format-15 (LLVM style) flagged two spots from the macOS
port batch:
* flare/fiber/detail/context.h: `} // extern "C"` -> `} // extern "C"`
(LLVM trailing-comment style wants two spaces before //).
* flare/io/detail/kqueue_poller.cc: the `read_flags`/`write_flags`
ternaries were hand-aligned to a column width that clang-format
didn't pick. Drop the explicit alignment and pull the disabled
case into a named `disable_flags` local so each assignment fits
on a single line that clang-format leaves alone.
No behaviour change.
* thirdparty/blade: bump blade.zip for target_arch cache fix
Picks up the toolchain.target_arch caching from
https://github.com/blade-build/blade-build/pull/1224.
BUILD-file load on this workspace drops from 13.2s to 0.25s.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
31 lines
1.3 KiB
Python
31 lines
1.3 KiB
Python
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
|
load("//bazel:deps.bzl", "flare_dependencies")
|
|
|
|
flare_dependencies()
|
|
|
|
# Prefer the host's preinstalled `make` over rules_foreign_cc's bundled one:
|
|
# the bundled make segfaults building jemalloc on macOS. Registered before
|
|
# `rules_foreign_cc_dependencies()` so it wins toolchain resolution. blade
|
|
# likewise drives every foreign build with the system make.
|
|
register_toolchains("@rules_foreign_cc//toolchains:preinstalled_make_toolchain")
|
|
|
|
load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
|
|
|
|
# This sets up some common toolchains for building targets. For more details, please see
|
|
# https://github.com/bazelbuild/rules_foreign_cc/tree/main/docs#rules_foreign_cc_dependencies
|
|
rules_foreign_cc_dependencies()
|
|
|
|
http_archive(
|
|
name = "com_google_googletest",
|
|
sha256 = "81964fe578e9bd7c94dfdb09c8e4d6e6759e19967e397dbea48d1c10e45d0df2",
|
|
strip_prefix = "googletest-release-1.12.1",
|
|
urls = ["https://github.com/google/googletest/archive/refs/tags/release-1.12.1.tar.gz"],
|
|
)
|
|
|
|
http_archive(
|
|
name = "com_github_google_benchmark",
|
|
sha256 = "e0e6a0f2a5e8971198e5d382507bfe8e4be504797d75bb7aec44b5ea368fa100",
|
|
strip_prefix = "benchmark-1.7.0",
|
|
urls = ["https://github.com/google/benchmark/archive/refs/tags/v1.7.0.zip"],
|
|
)
|