Compare commits

..

1 Commits

Author SHA1 Message Date
Claude
23f65520af fix(node): add missing hasSubscribers getter to TracingChannel
The `TracingChannel` class in `node:diagnostics_channel` was missing the
`hasSubscribers` getter, causing it to return `undefined` instead of a
boolean. This adds the getter which returns `true` if any of the five
sub-channels (start, end, asyncStart, asyncEnd, error) have subscribers,
matching Node.js behavior.

Closes #27805
2026-03-04 22:50:27 +00:00
3 changed files with 271 additions and 70 deletions

View File

@@ -4,87 +4,227 @@ if(NOT BUILDKITE_CACHE OR NOT BUN_LINK_ONLY)
return()
endif()
# This runs inside the ${targetKey}-build-bun step (see .buildkite/ci.mjs), which
# links artifacts from ${targetKey}-build-cpp and ${targetKey}-build-zig in the
# same build. Step keys follow a fixed ${targetKey}-build-{cpp,zig,bun} pattern
# and build-bun's depends_on lists exactly those two steps, so we derive the
# siblings by swapping the suffix on our own BUILDKITE_STEP_KEY.
optionx(BUILDKITE_ORGANIZATION_SLUG STRING "The organization slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_PIPELINE_SLUG STRING "The pipeline slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_BUILD_ID STRING "The build ID (UUID) to use on Buildkite")
optionx(BUILDKITE_BUILD_NUMBER STRING "The build number to use on Buildkite")
optionx(BUILDKITE_GROUP_ID STRING "The group ID to use on Buildkite")
if(NOT DEFINED ENV{BUILDKITE_STEP_KEY})
message(FATAL_ERROR "BUILDKITE_STEP_KEY is not set (expected inside a Buildkite job)")
if(ENABLE_BASELINE)
set(DEFAULT_BUILDKITE_GROUP_KEY ${OS}-${ARCH}-baseline)
else()
set(DEFAULT_BUILDKITE_GROUP_KEY ${OS}-${ARCH})
endif()
set(BUILDKITE_STEP_KEY $ENV{BUILDKITE_STEP_KEY})
if(NOT BUILDKITE_STEP_KEY MATCHES "^(.+)-build-bun$")
message(FATAL_ERROR "Unexpected BUILDKITE_STEP_KEY '${BUILDKITE_STEP_KEY}' (expected '<target>-build-bun')")
optionx(BUILDKITE_GROUP_KEY STRING "The group key to use on Buildkite" DEFAULT ${DEFAULT_BUILDKITE_GROUP_KEY})
if(BUILDKITE)
optionx(BUILDKITE_BUILD_ID_OVERRIDE STRING "The build ID to use on Buildkite")
if(BUILDKITE_BUILD_ID_OVERRIDE)
setx(BUILDKITE_BUILD_ID ${BUILDKITE_BUILD_ID_OVERRIDE})
endif()
endif()
set(BUILDKITE_TARGET_KEY ${CMAKE_MATCH_1})
# Download all artifacts from both sibling steps. The agent scopes to the
# current build via $BUILDKITE_BUILD_ID; --step resolves by key within it.
# git clean -ffxdq runs between builds (BUILDKITE_GIT_CLEAN_FLAGS), so
# ${BUILD_PATH} starts clean.
set(BUILDKITE_PATH ${BUILD_PATH}/buildkite)
set(BUILDKITE_BUILDS_PATH ${BUILDKITE_PATH}/builds)
file(MAKE_DIRECTORY ${BUILD_PATH})
if(NOT BUILDKITE_BUILD_ID)
# TODO: find the latest build on the main branch that passed
return()
endif()
foreach(SUFFIX cpp zig)
set(STEP ${BUILDKITE_TARGET_KEY}-build-${SUFFIX})
message(STATUS "Downloading artifacts from ${STEP}")
execute_process(
COMMAND buildkite-agent artifact download * . --step ${STEP}
WORKING_DIRECTORY ${BUILD_PATH}
RESULT_VARIABLE DOWNLOAD_RC
ERROR_VARIABLE DOWNLOAD_ERR
)
if(NOT DOWNLOAD_RC EQUAL 0)
message(FATAL_ERROR "buildkite-agent artifact download from ${STEP} failed: ${DOWNLOAD_ERR}")
endif()
endforeach()
# Use BUILDKITE_BUILD_NUMBER for the URL if available, as the UUID format causes a 302 redirect
# that CMake's file(DOWNLOAD) doesn't follow, resulting in empty response.
if(BUILDKITE_BUILD_NUMBER)
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_NUMBER})
else()
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_ID})
endif()
setx(BUILDKITE_BUILD_PATH ${BUILDKITE_BUILDS_PATH}/builds/${BUILDKITE_BUILD_ID})
# libbun-profile.a and libbun-asan.a are gzipped before upload (see
# register_command's ARTIFACTS handling in Globals.cmake). Windows .lib
# files are not gzipped, so this glob is empty there.
file(GLOB BUILDKITE_GZ_ARTIFACTS "${BUILD_PATH}/*.gz")
foreach(GZ ${BUILDKITE_GZ_ARTIFACTS})
message(STATUS "Unpacking ${GZ}")
execute_process(
COMMAND gunzip -f ${GZ}
RESULT_VARIABLE GUNZIP_RC
ERROR_VARIABLE GUNZIP_ERR
)
if(NOT GUNZIP_RC EQUAL 0)
message(FATAL_ERROR "gunzip ${GZ} failed: ${GUNZIP_ERR}")
endif()
endforeach()
# Artifacts are uploaded with subdirectory paths (lolhtml/release/liblolhtml.a,
# mimalloc/CMakeFiles/mimalloc-obj.dir/src/static.c.o, etc.) and the agent
# recreates that structure on download. Recurse, but skip top-level CMakeFiles/
# (our own compiler detection) and cache/ — nested CMakeFiles/ are real artifacts.
file(GLOB_RECURSE BUILDKITE_LINK_ARTIFACTS
"${BUILD_PATH}/*.o"
"${BUILD_PATH}/*.a"
"${BUILD_PATH}/*.lib"
file(
DOWNLOAD ${BUILDKITE_BUILD_URL}
HTTPHEADER "Accept: application/json"
TIMEOUT 15
STATUS BUILDKITE_BUILD_STATUS
${BUILDKITE_BUILD_PATH}/build.json
)
string(REGEX REPLACE "\\." "\\\\." BUILD_PATH_RE "${BUILD_PATH}")
list(FILTER BUILDKITE_LINK_ARTIFACTS EXCLUDE REGEX "^${BUILD_PATH_RE}/(CMakeFiles|cache)/")
if(NOT BUILDKITE_LINK_ARTIFACTS)
message(FATAL_ERROR "No linkable artifacts found in ${BUILD_PATH} after download")
if(NOT BUILDKITE_BUILD_STATUS EQUAL 0)
message(FATAL_ERROR "No build found: ${BUILDKITE_BUILD_STATUS} ${BUILDKITE_BUILD_URL}")
return()
endif()
list(LENGTH BUILDKITE_LINK_ARTIFACTS BUILDKITE_LINK_COUNT)
message(STATUS "Registered ${BUILDKITE_LINK_COUNT} linkable artifacts from ${BUILDKITE_TARGET_KEY}-build-{cpp,zig}")
# Register a no-op custom command for each linkable artifact so register_command
# (Globals.cmake) sees them as GENERATED and shims its own output to
# ${output}.always_run_${target} instead of overwriting them with a rebuild.
# With no DEPENDS, the no-op never fires — the file already exists.
file(READ ${BUILDKITE_BUILD_PATH}/build.json BUILDKITE_BUILD)
# CMake's string(JSON ...) interprets escape sequences like \n, \r, \t.
# We need to escape these specific sequences while preserving valid JSON escapes like \" and \\.
# Strategy: Use a unique placeholder to protect \\ sequences, escape \n/\r/\t, then restore \\.
# This prevents \\n (literal backslash + n) from being corrupted to \\\n.
set(BKSLASH_PLACEHOLDER "___BKSLASH_PLACEHOLDER_7f3a9b2c___")
string(REPLACE "\\\\" "${BKSLASH_PLACEHOLDER}" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\n" "\\\\n" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\r" "\\\\r" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\t" "\\\\t" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "${BKSLASH_PLACEHOLDER}" "\\\\" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
# CMake treats semicolons as list separators in unquoted variable expansions.
# Commit messages and other JSON string fields can contain semicolons, which would
# cause string(JSON) to receive garbled arguments. Escape them before parsing.
string(REPLACE ";" "\\;" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
foreach(ARTIFACT ${BUILDKITE_LINK_ARTIFACTS})
add_custom_command(
OUTPUT ${ARTIFACT}
COMMAND ${CMAKE_COMMAND} -E true
string(JSON BUILDKITE_BUILD_UUID GET "${BUILDKITE_BUILD}" id)
string(JSON BUILDKITE_JOBS GET "${BUILDKITE_BUILD}" jobs)
string(JSON BUILDKITE_JOBS_COUNT LENGTH "${BUILDKITE_JOBS}")
if(NOT BUILDKITE_JOBS_COUNT GREATER 0)
message(FATAL_ERROR "No jobs found: ${BUILDKITE_BUILD_URL}")
return()
endif()
set(BUILDKITE_JOBS_FAILED)
set(BUILDKITE_JOBS_NOT_FOUND)
set(BUILDKITE_JOBS_NO_ARTIFACTS)
set(BUILDKITE_JOBS_NO_MATCH)
set(BUILDKITE_JOBS_MATCH)
math(EXPR BUILDKITE_JOBS_MAX_INDEX "${BUILDKITE_JOBS_COUNT} - 1")
foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
string(JSON BUILDKITE_JOB GET "${BUILDKITE_JOBS}" ${i})
string(JSON BUILDKITE_JOB_ID GET "${BUILDKITE_JOB}" id)
string(JSON BUILDKITE_JOB_PASSED GET "${BUILDKITE_JOB}" passed)
string(JSON BUILDKITE_JOB_GROUP_ID GET "${BUILDKITE_JOB}" group_uuid)
string(JSON BUILDKITE_JOB_GROUP_KEY GET "${BUILDKITE_JOB}" group_identifier)
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" step_key)
if(NOT BUILDKITE_JOB_NAME)
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" name)
endif()
if(NOT BUILDKITE_JOB_PASSED)
list(APPEND BUILDKITE_JOBS_FAILED ${BUILDKITE_JOB_NAME})
continue()
endif()
if(NOT (BUILDKITE_GROUP_ID AND BUILDKITE_GROUP_ID STREQUAL BUILDKITE_JOB_GROUP_ID) AND
NOT (BUILDKITE_GROUP_KEY AND BUILDKITE_GROUP_KEY STREQUAL BUILDKITE_JOB_GROUP_KEY))
list(APPEND BUILDKITE_JOBS_NO_MATCH ${BUILDKITE_JOB_NAME})
continue()
endif()
set(BUILDKITE_ARTIFACTS_URL https://buildkite.com/organizations/${BUILDKITE_ORGANIZATION_SLUG}/pipelines/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_UUID}/jobs/${BUILDKITE_JOB_ID}/artifacts)
set(BUILDKITE_ARTIFACTS_PATH ${BUILDKITE_BUILD_PATH}/artifacts/${BUILDKITE_JOB_ID}.json)
file(
DOWNLOAD ${BUILDKITE_ARTIFACTS_URL}
HTTPHEADER "Accept: application/json"
TIMEOUT 15
STATUS BUILDKITE_ARTIFACTS_STATUS
${BUILDKITE_ARTIFACTS_PATH}
)
if(NOT BUILDKITE_ARTIFACTS_STATUS EQUAL 0)
list(APPEND BUILDKITE_JOBS_NOT_FOUND ${BUILDKITE_JOB_NAME})
continue()
endif()
file(READ ${BUILDKITE_ARTIFACTS_PATH} BUILDKITE_ARTIFACTS)
string(REPLACE ";" "\\;" BUILDKITE_ARTIFACTS "${BUILDKITE_ARTIFACTS}")
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH "${BUILDKITE_ARTIFACTS}")
if(NOT BUILDKITE_ARTIFACTS_LENGTH GREATER 0)
list(APPEND BUILDKITE_JOBS_NO_ARTIFACTS ${BUILDKITE_JOB_NAME})
continue()
endif()
math(EXPR BUILDKITE_ARTIFACTS_MAX_INDEX "${BUILDKITE_ARTIFACTS_LENGTH} - 1")
foreach(i RANGE 0 ${BUILDKITE_ARTIFACTS_MAX_INDEX})
string(JSON BUILDKITE_ARTIFACT GET "${BUILDKITE_ARTIFACTS}" ${i})
string(JSON BUILDKITE_ARTIFACT_ID GET "${BUILDKITE_ARTIFACT}" id)
string(JSON BUILDKITE_ARTIFACT_PATH GET "${BUILDKITE_ARTIFACT}" path)
if(NOT BUILDKITE_ARTIFACT_PATH MATCHES "\\.(o|a|lib|zip|tar|gz)")
continue()
endif()
if(BUILDKITE)
if(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-profile.a")
set(BUILDKITE_ARTIFACT_PATH libbun-profile.a.gz)
elseif(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-asan.a")
set(BUILDKITE_ARTIFACT_PATH libbun-asan.a.gz)
endif()
set(BUILDKITE_DOWNLOAD_COMMAND buildkite-agent artifact download ${BUILDKITE_ARTIFACT_PATH} . --build ${BUILDKITE_BUILD_UUID} --step ${BUILDKITE_JOB_ID})
else()
set(BUILDKITE_DOWNLOAD_COMMAND curl -L -o ${BUILDKITE_ARTIFACT_PATH} ${BUILDKITE_ARTIFACTS_URL}/${BUILDKITE_ARTIFACT_ID})
endif()
add_custom_command(
COMMENT
"Downloading ${BUILDKITE_ARTIFACT_PATH}"
VERBATIM COMMAND
${BUILDKITE_DOWNLOAD_COMMAND}
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/${BUILDKITE_ARTIFACT_PATH}
)
if(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-profile.a.gz")
add_custom_command(
COMMENT
"Unpacking libbun-profile.a.gz"
VERBATIM COMMAND
gunzip libbun-profile.a.gz
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/libbun-profile.a
DEPENDS
${BUILD_PATH}/libbun-profile.a.gz
)
elseif(BUILDKITE_ARTIFACT_PATH STREQUAL "libbun-asan.a.gz")
add_custom_command(
COMMENT
"Unpacking libbun-asan.a.gz"
VERBATIM COMMAND
gunzip libbun-asan.a.gz
WORKING_DIRECTORY
${BUILD_PATH}
OUTPUT
${BUILD_PATH}/libbun-asan.a
DEPENDS
${BUILD_PATH}/libbun-asan.a.gz
)
endif()
endforeach()
list(APPEND BUILDKITE_JOBS_MATCH ${BUILDKITE_JOB_NAME})
endforeach()
if(BUILDKITE_JOBS_FAILED)
list(SORT BUILDKITE_JOBS_FAILED COMPARE STRING)
list(JOIN BUILDKITE_JOBS_FAILED " " BUILDKITE_JOBS_FAILED)
message(WARNING "The following jobs were found, but failed: ${BUILDKITE_JOBS_FAILED}")
endif()
if(BUILDKITE_JOBS_NOT_FOUND)
list(SORT BUILDKITE_JOBS_NOT_FOUND COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NOT_FOUND " " BUILDKITE_JOBS_NOT_FOUND)
message(WARNING "The following jobs were found, but could not fetch their data: ${BUILDKITE_JOBS_NOT_FOUND}")
endif()
if(BUILDKITE_JOBS_NO_MATCH)
list(SORT BUILDKITE_JOBS_NO_MATCH COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NO_MATCH " " BUILDKITE_JOBS_NO_MATCH)
message(WARNING "The following jobs were found, but did not match the group ID: ${BUILDKITE_JOBS_NO_MATCH}")
endif()
if(BUILDKITE_JOBS_NO_ARTIFACTS)
list(SORT BUILDKITE_JOBS_NO_ARTIFACTS COMPARE STRING)
list(JOIN BUILDKITE_JOBS_NO_ARTIFACTS " " BUILDKITE_JOBS_NO_ARTIFACTS)
message(WARNING "The following jobs were found, but had no artifacts: ${BUILDKITE_JOBS_NO_ARTIFACTS}")
endif()
if(BUILDKITE_JOBS_MATCH)
list(SORT BUILDKITE_JOBS_MATCH COMPARE STRING)
list(JOIN BUILDKITE_JOBS_MATCH " " BUILDKITE_JOBS_MATCH)
message(STATUS "The following jobs were found, and matched the group ID: ${BUILDKITE_JOBS_MATCH}")
endif()
if(NOT BUILDKITE_JOBS_FAILED AND NOT BUILDKITE_JOBS_NOT_FOUND AND NOT BUILDKITE_JOBS_NO_MATCH AND NOT BUILDKITE_JOBS_NO_ARTIFACTS AND NOT BUILDKITE_JOBS_MATCH)
message(FATAL_ERROR "Something went wrong with Buildkite?")
endif()

View File

@@ -255,6 +255,16 @@ class TracingChannel {
asyncEnd;
error;
get hasSubscribers() {
return (
this.start.hasSubscribers ||
this.end.hasSubscribers ||
this.asyncStart.hasSubscribers ||
this.asyncEnd.hasSubscribers ||
this.error.hasSubscribers
);
}
constructor(nameOrChannels) {
if (typeof nameOrChannels === "string") {
this.start = channel(`tracing:${nameOrChannels}:start`);

View File

@@ -0,0 +1,51 @@
'use strict';
const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');
const handler = common.mustNotCall();
{
const handlers = {
start: common.mustNotCall()
};
const channel = dc.tracingChannel('test');
assert.strictEqual(channel.hasSubscribers, false);
channel.subscribe(handlers);
assert.strictEqual(channel.hasSubscribers, true);
channel.unsubscribe(handlers);
assert.strictEqual(channel.hasSubscribers, false);
channel.start.subscribe(handler);
assert.strictEqual(channel.hasSubscribers, true);
channel.start.unsubscribe(handler);
assert.strictEqual(channel.hasSubscribers, false);
}
{
const handlers = {
asyncEnd: common.mustNotCall()
};
const channel = dc.tracingChannel('test');
assert.strictEqual(channel.hasSubscribers, false);
channel.subscribe(handlers);
assert.strictEqual(channel.hasSubscribers, true);
channel.unsubscribe(handlers);
assert.strictEqual(channel.hasSubscribers, false);
channel.asyncEnd.subscribe(handler);
assert.strictEqual(channel.hasSubscribers, true);
channel.asyncEnd.unsubscribe(handler);
assert.strictEqual(channel.hasSubscribers, false);
}