Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC Commits: b3b89d32 by mangoiv at 2026-10-05T22:03:32-04:00 hadrian: Persist the used flavour name in the build root (#20650) This executes the idea proposed in #20650, teaching Hadrian to persist the flavour used to build a build root. This both makes use of multiple build roots more ergonomic (as the user no longer needs to repeat themselves) and avoids a tiresome foot-gun (forgetting to specify a flavour, often wholly invalidating a build root). Closes #20650. Co-authored-by: Sylvain Henry <sylvain@haskus.fr> - - - - - f977837b by mangoiv at 2026-10-05T22:04:21-04:00 configure: fix sed script and subst tooldir script Fixes #27672 Adds sanity checking for SUBST_TOOLDIR Metric Decrease: LinkableUsage01 MultiLayerModulesTH_OneShot T10547 T12234 T12425 T13035 T13820 T18140 T18730 T18923 T9198 TcPlugin_RewritePerf mhu-perf - - - - - f1cb08be by Hannes Siebenhandl at 2026-10-05T22:05:03-04:00 Prepend in-memory unit database for backpack The packageDbFlags are stored in reverse order. Thus, prepending will ensure that the in-memory unit database is the last item on the unit database stack. Further, the in-memory unit database needs to be able to shadow units from earlier unit databases in the unit database stack. Moreover, a `-clear-package-db` would remove the in-memory database if it weren't the last item on the stack. Closes #27835 - - - - - 64ce7e20 by mangoiv at 2026-10-06T10:05:03-04:00 ci: move hadrian/ghci into the lint category so it can be picked up by more runners Fixes #27817 - - - - - 5fe7e9db by mangoiv at 2026-10-06T10:05:03-04:00 ci: make lint-adjacent jobs into platform independent - move lint jobs into lint-anyarch tag - unify lint jobs under the same platform independent container - - - - - 5d7d60d1 by Kazu Yamamoto at 2026-10-06T10:05:12-04:00 Do not leave standard file descriptors free at startup (#24450) A process may be started with stdout closed -- `prog >&-` from a shell does exactly that. Nothing then reserves descriptor 1, and open(), pipe() and socket() hand out the lowest free descriptor, so the next descriptor the runtime creates for itself takes it: the ticker's pipe, or a capability's IO manager control pipe. 'GHC.Internal.IO.StdHandles.stdout' is a CAF, built the first time the program writes to stdout, which is long after that has happened. It builds a 'Handle' over descriptor 1, which now belongs to the runtime, and nothing reports an error because the descriptor is perfectly good. The program then writes its output into the runtime's own plumbing. What that looks like from the outside is a hang. The descriptor the Handle has taken over is the read end of a pipe, so poll() for POLLOUT on it never says yes and epoll_ctl() accepts a registration for it; the thread waiting to write blocks for ever. Closing the Handle at exit closes the runtime's descriptor, which is worse: the ticker then fails on every tick. 'hs_init_ghc' now opens /dev/null onto whichever of descriptors 0, 1 and 2 are closed, before the runtime creates any descriptor of its own. Each is opened for the access that descriptor would normally have, read-only for 0 and write-only for 1 and 2, so that writing to 'stdin' or reading from 'stdout' still fails rather than quietly succeeding against /dev/null. See Note [Standard file descriptors must be open]. Moving only the descriptors the runtime opens would not do: the first file the program itself opened would land on the free number instead. The fix has to leave no free number. A program run this way therefore finds stdout connected to /dev/null rather than failing with EBADF on the first write, which is what such a program would get from most other language runtimes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> - - - - - 03383bf1 by Simon Jakobi at 2026-10-06T10:05:13-04:00 changelog: Repair the sentence describing #27477 The description read "programs using could crash", the object of "using" having been lost. The synopsis already says which programs are affected, so drop the dangling word. Assisted-by: Claude Fable 5.1 - - - - - 18 changed files: - .gitlab-ci.yml - + changelog.d/24450 - changelog.d/fix-parallel-gc-selector-race-27477 - + changelog.d/hadrian-persist-flavour - changelog.d/unit-index - compiler/GHC/Driver/Backpack.hs - distrib/configure.ac.in - hadrian/doc/flavours.md - hadrian/src/Rules.hs - hadrian/src/Settings.hs - m4/subst_tooldir.m4 - rts/RtsStartup.c - + testsuite/tests/backpack/should_compile/T27835.bkp - + testsuite/tests/backpack/should_compile/T27835.stderr - testsuite/tests/backpack/should_compile/all.T - + testsuite/tests/rts/T24450.hs - + testsuite/tests/rts/T24450.stderr - testsuite/tests/rts/all.T Changes: ===================================== .gitlab-ci.yml ===================================== @@ -137,15 +137,37 @@ workflow: .lint: stage: tool-lint tags: - - lint - before_script: - # Allow Git to read from the runner-owned checkout. This is needed - # for example by the lint-author job. - - git config --global --add safe.directory "$CI_PROJECT_DIR" + - lint-anyarch + image: debian:13 rules: - *drafts-can-fail-lint - when: always +.install-ghc-toolchain: &install-ghc-toolchain + - apt update + - apt install -qy build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev libncurses6 libtinfo6 pkg-config python3 cmake git happy alex autoconf automake python3 libgmp-dev libnuma-dev libncurses-dev + - curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh + - source /root/.ghcup/env + - ghcup --no-verbose install ghc --set --install-targets 'install_bin install_lib update_package_db' '9.10' + - ghcup --no-verbose install cabal --set + - ghc --info + - cabal --version + +# linting but we need a boot toolchain +.lint-ghc-toolchain: + extends: .lint + before_script: + - *install-ghc-toolchain + - git config --global --add safe.directory "$CI_PROJECT_DIR" + +# linting but we need python +.lint-python: + extends: .lint + before_script: + - apt update + - apt install -qy python3 mypy shellcheck git + - git config --global --add safe.directory "$CI_PROJECT_DIR" + .nightly: &nightly variables: XZ_OPT: "-9" @@ -217,11 +239,11 @@ not-interruptible: stage: not-interruptible script: "true" interruptible: false - image: "debian:13" + image: debian:13 variables: GIT_STRATEGY: none tags: - - lint + - lint-anyarch rules: # - if: '$CI_COMMIT_BRANCH == "master"' # when: always @@ -242,7 +264,6 @@ include: '.gitlab/jobs.yaml' ghc-linters: stage: tool-lint - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" extends: .lint-params variables: BUILD_FLAVOUR: default @@ -258,16 +279,11 @@ ghc-linters: # Skipped if the MR has the ~"no-changelog" label. lint-changelog: stage: tool-lint - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" - extends: .lint + extends: .lint-ghc-toolchain variables: CHANGELOG_EXPECT_MR: "$CI_MERGE_REQUEST_IID" GIT_DEPTH: 1 GIT_SUBMODULE_STRATEGY: none - before_script: - - export PATH="/opt/toolchain/bin:$PATH" - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - .gitlab/ci.sh lint_changelog dependencies: [] @@ -279,42 +295,30 @@ lint-changelog: # Run mypy Python typechecker on linter scripts. lint-linters: - image: "registry.gitlab.haskell.org/ghc/ci-images/linters:$DOCKER_REV" - extends: .lint + extends: .lint-python variables: GIT_DEPTH: 1 GIT_SUBMODULE_STRATEGY: none - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - mypy testsuite/tests/linters/regex-linters/*.py dependencies: [] # Check that .T files all parse by listing broken tests. lint-testsuite: - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" - extends: .lint + extends: .lint-ghc-toolchain variables: GIT_DEPTH: 1 GIT_SUBMODULE_STRATEGY: none - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - - make -Ctestsuite list_broken TEST_HC=$GHC + - make -Ctestsuite list_broken TEST_HC=$(which ghc) dependencies: [] # Run mypy Python typechecker on testsuite driver typecheck-testsuite: - image: "registry.gitlab.haskell.org/ghc/ci-images/linters:$DOCKER_REV" - extends: .lint + extends: .lint-python variables: GIT_DEPTH: 1 GIT_SUBMODULE_STRATEGY: none - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - mypy testsuite/driver/runtests.py dependencies: [] @@ -323,7 +327,6 @@ typecheck-testsuite: # accommodate, e.g., haddock changes not yet upstream) but not on `master` or # Marge jobs. .lint-submods: - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" extends: .lint-params variables: BUILD_FLAVOUR: default @@ -341,13 +344,9 @@ typecheck-testsuite: # Check that commits in an MR were not authored with the identity that CI uses # when it needs to create commits itself. This only needs Git history. lint-author: - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" - extends: .lint + extends: .lint-python variables: GIT_SUBMODULE_STRATEGY: none - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - git fetch "$CI_MERGE_REQUEST_PROJECT_URL" $CI_MERGE_REQUEST_TARGET_BRANCH_NAME - base="$(git merge-base FETCH_HEAD $CI_COMMIT_SHA)" @@ -425,12 +424,9 @@ lint-submods-branch: .lint-params: needs: [] - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" extends: .lint before_script: - - export PATH="/opt/toolchain/bin:$PATH" - # workaround for docker permissions - - sudo chown ghc:ghc -R . + - *install-ghc-toolchain - .gitlab/ci.sh setup after_script: - .gitlab/ci.sh save_cache @@ -448,18 +444,13 @@ lint-submods-branch: hadrian-ghc-in-ghci: stage: quick-build + extends: .lint-ghc-toolchain needs: - job: lint-linters - job: lint-submods optional: true - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . variables: GHC_FLAGS: -Werror -Wwarn=unused-imports - tags: - - x86_64-linux script: - git clean -xdf && git submodule foreach git clean -xdf - . .gitlab/ci.sh setup @@ -493,9 +484,6 @@ hadrian-multi: optional: true dependencies: null image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . variables: GHC_FLAGS: "-Wwarn=deprecations -Wwarn=unused-imports" CONFIGURE_ARGS: --enable-bootstrap-with-devel-snapshot @@ -544,6 +532,7 @@ stack-hadrian-build: extends: hadrian-ghc-in-ghci stage: quick-build script: + - ghcup --no-verbose install stack --set - . .gitlab/ci.sh setup - . .gitlab/ci.sh configure - hadrian/build-stack --version @@ -1050,9 +1039,6 @@ perf: image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV" tags: - x86_64-linux-perf - before_script: - # workaround for docker permissions - - sudo chown ghc:ghc -R . script: - root=$(pwd)/ghc - | ===================================== changelog.d/24450 ===================================== @@ -0,0 +1,14 @@ +section: rts +synopsis: + Fix a hang when a program is started with a standard file descriptor closed +description: + A process started with stdout or stderr closed -- ``prog >&-`` from a shell + does exactly that -- left that descriptor number free for the runtime to + take for its own use. The ``Handle`` for the stream, built later when the + program first wrote to it, then took over a descriptor belonging to the + runtime: the program wrote its output into the runtime's own plumbing and + hung, with no error reported anywhere. The runtime now opens ``/dev/null`` + onto whichever of the descriptors 0, 1 and 2 are closed when it starts, so + such a stream is connected to ``/dev/null`` instead. +mrs: !16710 +issues: #24450 ===================================== changelog.d/fix-parallel-gc-selector-race-27477 ===================================== @@ -1,7 +1,7 @@ section: rts synopsis: Fix a parallel GC race on weakly-ordered architectures (AArch64) that could crash or silently corrupt the heap description: - On weakly-ordered architectures such as AArch64 programs using + On weakly-ordered architectures such as AArch64, programs could crash during parallel garbage collection (segmentation fault or ``internal error: eval_thunk_selector: strange selectee <n>``) or continue running with silently corrupted heap data, due to a missing ===================================== changelog.d/hadrian-persist-flavour ===================================== @@ -0,0 +1,7 @@ +section: packaging +synopsis: Hadrian now persists the flavour name in the build root. When + rebuilding, the previously used flavour is automatically restored, removing + the need to re-specify it on every invocation and avoiding accidental build + root invalidation. +issues: #20650 +mrs: !7017 ===================================== changelog.d/unit-index ===================================== @@ -1,7 +1,7 @@ section: compiler synopsis: Use global ``UnitIndex`` to deduplicate ``UnitInfo``s over multiple home units -issues: #27500 #26423 #27748 #27834 -mrs: !16115 !16598 !16743 +issues: #27500 #26423 #27748 #27834 #27835 +mrs: !16115 !16598 !16743 !16748 description: { The ``UnitState`` used to be duplicated for all ``HomeUnitEnv``, not sharing any of the ``UnitInfo``s. ===================================== compiler/GHC/Driver/Backpack.hs ===================================== @@ -441,9 +441,11 @@ addInMemoryDatabase dflags u = do } let uic = hscUIC hsc_env liftIO $ cacheExternalUnitDatabase uic newdb - -- added at the end because ordering matters + -- Ordering matters here! We prepend the new unit database, because 'packageDBFlags' + -- works like a stack, where what's on top takes precedence over what's below (same as right-to-left when given at the CLI). + -- The new db needs highest precedence to overwrite any other in-memory units that potentially occur in the dbs below it. pure dflags - { packageDBFlags = packageDBFlags dflags ++ [PackageDB (PkgDbPath (unitDatabasePath newdb))] + { packageDBFlags = PackageDB (PkgDbPath (unitDatabasePath newdb)) : packageDBFlags dflags } -- | Register a new virtual unit database containing a single unit ===================================== distrib/configure.ac.in ===================================== @@ -103,7 +103,7 @@ AC_ARG_ENABLE(distro-toolchain, AC_SUBST([EnableDistroToolchain]) if test "$HostOS" = "mingw32" -a "$EnableDistroToolchain" = "NO"; then - FP_SETUP_WINDOWS_TOOLCHAIN([$hardtop/mingw/], [\$\$topdir/../mingw/]) + FP_SETUP_WINDOWS_TOOLCHAIN([$hardtop/mingw/], [\$topdir/../mingw/]) fi ===================================== hadrian/doc/flavours.md ===================================== @@ -3,7 +3,14 @@ Hadrian supports a few predefined _build flavours_, i.e. collections of build settings that fully define a GHC build (see `src/Flavour.hs`). Users can add their own build flavours if need be, as described -[here](https://gitlab.haskell.org/ghc/ghc/blob/master/hadrian/doc/user-settings.md#...). +[here](https://gitlab.haskell.org/ghc/ghc/blob/master/hadrian/doc/user-settings.md#...) or modify existing flavours using the flavour transformers notion seem below. + +The flavour to be used for a build root can be specified on the command-line +via the `--flavour` flag. Moreover, this flavour name is saved in the build +root; future invocations of Hadrian without a `--flavour` flag will default to +the last-used flavour. + +See `Note [Persisting the flavour]` in `hadrian/src/Settings.hs` ## Arguments ===================================== hadrian/src/Rules.hs ===================================== @@ -129,6 +129,7 @@ packageRules = do buildRules :: Rules () buildRules = do + Settings.flavourFileRules Rules.BinaryDist.bindistRules Rules.Generate.copyRules Rules.Generate.generateRules ===================================== hadrian/src/Settings.hs ===================================== @@ -1,10 +1,14 @@ +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeFamilies #-} + module Settings ( getExtraArgs, getArgs, getLibraryWays, getRtsWays, flavour, knownPackages, findPackageByName, unsafeFindPackageByName, unsafeFindPackageByPath, isLibrary, stagePackages, getBignumBackend, completeSetting, queryBuildTarget, queryHostTarget, queryBuild, queryHost, - queryArch, queryOS, queryVendor + queryArch, queryOS, queryVendor, + flavourFileRules ) where import CommandLine @@ -58,6 +62,56 @@ hadrianFlavours = , ghcInGhciFlavour, validateFlavour, slowValidateFlavour ] +{- +Note [Persisting the flavour] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Passing the flavour on the command-line means the user must consistently pass +the same --flavour flag to all Hadrian invocations on a particular build root. +Forgetting even once can result in a full rebuild of the tree. + +To avoid this we persist the flavour name in the build root itself +(specifically, in `<build root>/flavour`). We refer to this file when looking +up the active flavour, defaulting to it if the user does not specify a flavour +name explicitly. +-} + +-- | Oracle query type for the active flavour name. +-- See Note [Persisting the flavour]. +newtype FlavourName = FlavourName () + deriving (Binary, Eq, Hashable, NFData, Show) +type instance RuleResult FlavourName = String + +flavourFileRules :: Rules () +flavourFileRules = do + root <- buildRootRules + + -- Cache the flavour name lookup for the whole Shake run. + -- alwaysRerun ensures the oracle re-executes on every new invocation so + -- that a changed --flavour flag is never ignored due to a stale cross-run + -- cache entry (CLI args are not tracked as Shake dependencies). + -- See Note [Persisting the flavour]. + void $ addOracleCache $ \(FlavourName _) -> do + alwaysRerun + let flavourFile = root -/- "flavour" + cmdFlavour >>= \case + Just nm -> do + putBuild $ "Flavour: " ++ nm + liftIO $ writeFile flavourFile nm + return nm + Nothing -> do + exists <- doesFileExist flavourFile + if exists + then do nm <- liftIO $ readFile flavourFile + putBuild $ "Flavour: " ++ nm ++ " (cached from last build, override by passing --flavour=... explicitly)" + return nm + else return userDefaultFlavour + +-- | Lookup the requested flavour name, referring to the persistent flavour +-- file saved from the previous Hadrian execution if not specified. +-- The result is cached for the whole Shake run via the 'FlavourName' oracle. +getFlavourName :: Action String +getFlavourName = askOracle (FlavourName ()) + -- | This action looks up a flavour with the name given on the -- command line with @--flavour@, defaulting to 'userDefaultFlavour' -- when no explicit @--flavour@ is passed. It then applies any @@ -66,7 +120,7 @@ hadrianFlavours = -- syntax. See Note [Hadrian settings] at the bottom of this file. flavour :: Action Flavour flavour = do - flavourName <- fromMaybe userDefaultFlavour <$> cmdFlavour + flavourName <- getFlavourName when ("quickest" `isPrefixOf` flavourName) $ fail $ "The `quickest` flavour has been deprecated. Use `quick` instead.\n" ++ "Skip building dynamic libraries with `quick+no_dynamic_libs`\n" ===================================== m4/subst_tooldir.m4 ===================================== @@ -41,5 +41,5 @@ dnl ghc-toolchain. # See Note [How we configure the bundled windows toolchain] AC_DEFUN([SUBST_TOOLDIR], [ - sed -i.bkp $1 's%'"$mingw_prefix"'%'"$mingw_install_prefix"'%g' + sed -i.bkp 's%'"$mingw_prefix"'%'"$mingw_install_prefix"'%g' $1 ]) ===================================== rts/RtsStartup.c ===================================== @@ -69,6 +69,10 @@ #if defined(HAVE_LOCALE_H) #include <locale.h> #endif +#if !defined(mingw32_HOST_OS) +#include <errno.h> +#include <fcntl.h> +#endif // Count of how many outstanding hs_init()s there have been. static StgWord hs_init_count = 0; @@ -81,6 +85,10 @@ static int64_t __codePage = -1; static void flushStdHandles(void); +#if !defined(mingw32_HOST_OS) +static void sanitiseStandardFds(void); +#endif + /* ----------------------------------------------------------------------------- Initialise floating point unit on x86 (currently disabled; See Note [x86 Floating point precision] in compiler/GHC/CmmToAsm/X86/Instr.hs) @@ -238,6 +246,82 @@ hs_init_with_rtsopts(int *argc, char **argv[]) void init_ghc_hs_iface(void); +#if !defined(mingw32_HOST_OS) +/* See Note [Standard file descriptors must be open]. */ +static void sanitiseStandardFds(void) +{ + // The access mode each descriptor would have had, had the process been + // started with it open. + static const int access_mode[3] = { O_RDONLY, O_WRONLY, O_WRONLY }; + + for (int fd = 0; fd <= 2; fd++) { + if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) { + continue; // already open + } + + int nullfd; + do { + nullfd = open("/dev/null", access_mode[fd]); + } while (nullfd == -1 && errno == EINTR); + + if (nullfd == -1) { + // No /dev/null to be had. There is nothing better to do than + // carry on; the hazard the Note describes is then still there, + // but refusing to start would be worse. + return; + } + if (nullfd != fd) { + // open() hands out the lowest free descriptor, so this cannot + // happen unless something in this process is opening descriptors + // concurrently. Do not shuffle its descriptor around. + close(nullfd); + return; + } + } +} + +/* + * Note [Standard file descriptors must be open] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * A process may be started with any of file descriptors 0, 1 and 2 closed -- + * from a shell, `prog >&-` does exactly that. Nothing then reserves those + * numbers, and open(), pipe() and socket() hand out the lowest free + * descriptor, so the next descriptor the runtime creates for itself takes one + * of them: the ticker's pipe, or a capability's IO manager control pipe. + * + * Nothing warns anybody. 'GHC.Internal.IO.StdHandles.stdout' is a CAF, so it + * is built the first time the program writes to stdout, which is long after + * that has happened, and it builds a 'Handle' over descriptor 1 -- which now + * belongs to the runtime. The program then writes its output into the + * runtime's own plumbing, and at exit closes it. + * + * What this looks like from the outside is a hang, because the descriptor the + * Handle has taken over is the read end of a pipe. It is a perfectly good + * descriptor, so nothing reports an error: poll() for POLLOUT on it simply + * never says yes, epoll_ctl() accepts a registration for it, and the thread + * waiting to write blocks for ever. See #24450, where this is `prog >&-` + * hanging after main has finished. + * + * Closing those descriptors is legal, so the runtime cannot refuse them; and + * moving only the descriptors the runtime opens would not help, since the + * first file the program itself opens would land on the free number instead. + * The fix is to leave no free number: open /dev/null onto whichever of 0, 1 + * and 2 are closed, before the runtime creates any descriptor of its own. + * Other runtimes do the same, and for the same reason. + * + * A program run this way therefore finds stdout connected to /dev/null rather + * than failing with EBADF on the first write. That is the price of the fix, + * and it is the behaviour such a program would get from most other language + * runtimes. + * + * Each substitute is opened for the access the descriptor would normally have: + * read-only for 0, write-only for 1 and 2. Opening all three for both would + * make the substitute something a real stdin or stdout is not, and writing to + * 'stdin' or reading from 'stdout' would then quietly succeed instead of + * failing with EBADF. + */ +#endif + void hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config) { @@ -252,6 +336,12 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config) stg_exit(1); } +#if !defined(mingw32_HOST_OS) + /* Before anything here opens a file descriptor of its own. + See Note [Standard file descriptors must be open]. */ + sanitiseStandardFds(); +#endif + #if defined(wasm32_HOST_ARCH) char *pwd = getenv("PWD"); if (pwd != NULL) { ===================================== testsuite/tests/backpack/should_compile/T27835.bkp ===================================== @@ -0,0 +1,11 @@ +unit p where + module P where + p :: Int + p = 42 + +unit q where + dependency p + module Q where + import P + q :: Int + q = p + 1 ===================================== testsuite/tests/backpack/should_compile/T27835.stderr ===================================== @@ -0,0 +1,7 @@ +[1 of 2] Processing p + Instantiating p + [1 of 1] Compiling P ( p/P.hs, T27835.out/p/P.o ) +[2 of 2] Processing q + Instantiating q + [1 of 1] Including p + [1 of 1] Compiling Q ( q/Q.hs, T27835.out/q/Q.o ) ===================================== testsuite/tests/backpack/should_compile/all.T ===================================== @@ -61,3 +61,4 @@ test('T13250', normal, backpack_compile, ['']) test('T13323', normal, backpack_compile, ['']) test('T20396', normal, backpack_compile, ['']) test('T23424', [ignore_stdout, ignore_stderr], backpack_compile, ['-ddump-rn-trace -ddump-if-trace -ddump-tc-trace']) +test('T27835', normal, backpack_compile, ['-clear-package-db -global-package-db']) ===================================== testsuite/tests/rts/T24450.hs ===================================== @@ -0,0 +1,41 @@ +{-# LANGUAGE ScopedTypeVariables #-} +-- A process may be started with stdout closed -- `prog >&-` from a shell does +-- exactly that. Nothing then reserves descriptor 1, so the next descriptor +-- the runtime opens for itself takes it, and the 'Handle' for stdout, built +-- later when the program first writes, takes over a descriptor belonging to +-- the runtime. The write then goes into the runtime's own plumbing, and what +-- this looks like from the outside is a hang. See #24450 and +-- Note [Standard file descriptors must be open] in rts/RtsStartup.c. +-- +-- This test is run with its stdin and stdout closed, so what it has to say +-- goes to stderr. The four writes are the reproducer from the ticket and must +-- simply finish. The lines after them are what tell the two outcomes apart: +-- the descriptors the runtime opens for itself are pipes, so finding a pipe on +-- descriptor 0 or 1 means it was handed to the runtime. +module Main (main) where + +import Control.Exception (IOException, try) +import System.IO +import System.Posix.Files (getFdStatus, isCharacterDevice, isNamedPipe) +import System.Posix.Types (Fd (..)) + +main :: IO () +main = do + hPutChar stderr 'A' -- unbuffered + hPutChar stdout 'X' -- buffered; this is the one that used to hang + hPutChar stderr 'Z' -- unbuffered + hPutChar stderr '\n' -- unbuffered + mapM_ (\fd -> hPutStrLn stderr =<< describeFd fd) [0, 1] + +describeFd :: Fd -> IO String +describeFd fd = do + r <- try (getFdStatus fd) + return $ "fd " ++ show fd ++ ": " ++ case r of + Left (_ :: IOException) -> "closed" + Right st + -- What the fix arranges: /dev/null, opened onto the free number + -- before the runtime could take it. + | isCharacterDevice st -> "character device" + -- What #24450 is: the runtime's ticker or control pipe. + | isNamedPipe st -> "pipe" + | otherwise -> "something else" ===================================== testsuite/tests/rts/T24450.stderr ===================================== @@ -0,0 +1,3 @@ +AZ +fd 0: character device +fd 1: character device ===================================== testsuite/tests/rts/all.T ===================================== @@ -408,6 +408,18 @@ test('T10590', [ ignore_stderr , req_process ], compile_and_run, ['']) +# Run with stdin and stdout closed -- that is what leaves those descriptors +# free for the runtime to take, which is the whole of #24450. What the test +# has to say therefore goes to stderr and is checked against T24450.stderr. +test('T24450', + [ when(opsys('mingw32'), skip) # POSIX only + , js_skip # JS backend has its own runtime + # The wrapper below would close the stdout of the wasm runtime rather + # than that of the program running under it, which tests nothing. + , when(arch('wasm32'), skip) + , cmd_wrapper(lambda c: c + ' <&- >&-') + ], compile_and_run, ['']) + # 20000 was easily enough to trigger the bug with 7.10 test('T10904', [ extra_run_opts('20000'), req_c ], compile_and_run, ['T10904lib.c']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/aee5101e777cf926166646fbfe8214b... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/aee5101e777cf926166646fbfe8214b... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
participants (1)
-
Marge Bot (@marge-bot)