Conversation
…tion During parallel compilation, `Code.ensure_compiled/1` can return before the module's `__before_compile__` callbacks have finished executing. This causes a race condition where `function_exported?(module, :__channel_operations__, 0)` returns false for the first event processed from a handler module, because the `__channel_operations__/0` function is defined in ChannelSpec.Operations' `__before_compile__` callback. The fix adds a call to `module.module_info()` after `Code.ensure_compiled!/1`, which forces the BEAM VM to fully load the module, ensuring all compile-time callbacks have completed before we check for exported functions. This was causing intermittent issues where channel operations would randomly appear or disappear from the generated schema depending on compilation order.
a4e04a9 to
4f276ad
Compare
The original code used Code.ensure_compiled/1 (non-bang) but then expected
the module to be available for function_exported? checks. According to the
Elixir documentation:
> "If you are using Code.ensure_compiled/1, you are implying you may
> continue without the module and therefore Elixir may return
> {:error, :unavailable} for cases where the module is not yet available"
And explicitly warns against this pattern:
> "For those reasons, developers must typically use Code.ensure_compiled!/1"
The bang version "halts the compilation of the caller until the module
given to ensure_compiled!/1 becomes available", which is the correct
behavior when we need to check function_exported? on the module.
This was causing intermittent missing entries in channel_schema.json
(e.g., sources:create would be missing after clean compiles).
Verified fix:
- Without fix (ensure_compiled): 5/5 clean compiles -> sources:create MISSING
- With fix (ensure_compiled!): 5/5 clean compiles -> sources:create PRESENT
4f276ad to
318dab8
Compare
- Change ubuntu-20.04 to ubuntu-latest (fixes stuck runners) - Update Elixir test matrix to 1.15.7, 1.16.3, 1.17.3, 1.18.4 - Update quality checks to use Elixir 1.18.4 / OTP 27.2
axelson
approved these changes
Jan 29, 2026
Contributor
axelson
left a comment
There was a problem hiding this comment.
I tested this on the main repo with {:channel_spec, git: "https://github.com/felt/channel_spec.git", branch: "fix/ensure-module-fully-loaded-before-checking-exports"}
and ran mix clean and partial compiles and the json was successfully regenerated! Thanks for looking into this!
We'll want to follow this up with a version bump
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes intermittent missing entries in
channel_schema.json(e.g.,sources:createwould sometimes be missing after clean compiles).The Fix
Change
Code.ensure_compiled(module)toCode.ensure_compiled!(module)(add the bang).From the Elixir 1.18.4 Code documentation:
And from
ensure_compiled!/1:The original code used
ensure_compiled/1(non-bang) but then expected the module to be available forfunction_exported?checks. Whenensure_compiled/1returned{:error, :unavailable}, the code silently skipped the module. The bang version properly blocks until the module is ready.Why the Original Commit Worked
The original PR accidentally changed
ensure_compiledtoensure_compiled!(with bang) while also addingmodule_info/0calls. Theensure_compiled!change was the actual fix - themodule_info/0calls were unnecessary. The explanation about__before_compile__timing was incorrect.Verification
Tested in the Felt codebase by modifying
deps/channel_spec/lib/channel_spec/socket.ex:Without fix (
Code.ensure_compiled):With fix (
Code.ensure_compiled!):All 5 clean compiles consistently show
sources:createpresent with the fix.