Skip to content

build: finalize embedded python before exit#1707

Open
cpunion wants to merge 3 commits intogoplus:mainfrom
cpunion:fix/issue-487-python-flush
Open

build: finalize embedded python before exit#1707
cpunion wants to merge 3 commits intogoplus:mainfrom
cpunion:fix/issue-487-python-flush

Conversation

@cpunion
Copy link
Copy Markdown
Collaborator

@cpunion cpunion commented Mar 12, 2026

Summary

  • call Py_Finalize() for llgo programs that initialize embedded Python
  • flush Python buffered stdout/stderr before process exit
  • cover the generated entry module in internal/build tests

Fixes #487

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the stability and correctness of llgo programs that embed a Python interpreter. By explicitly finalizing the Python interpreter and flushing its output buffers before program termination, it prevents potential resource leaks and ensures all output is correctly processed, addressing a known issue related to Python's lifecycle management within the llgo environment.

Highlights

  • Python Finalization: Implemented the call to Py_Finalize() for llgo programs that initialize an embedded Python interpreter, ensuring proper resource cleanup upon program exit.
  • Buffered Output Flushing: Ensured Python's buffered standard output and error streams are flushed before the process exits, preventing data loss.
  • Test Coverage: Extended internal build tests to cover the generated entry module, specifically verifying the inclusion of the Py_Finalize() call.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/build/main_module.go
    • Declared and passed the Py_Finalize function to the entry point definition.
    • Added a conditional call to Py_Finalize within the program's entry function to ensure proper Python interpreter shutdown.
  • internal/build/main_module_test.go
    • Added a test assertion to verify that the Py_Finalize() call is present in the generated executable's LLVM IR.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly adds a call to Py_Finalize() for programs that use the embedded Python interpreter, ensuring proper cleanup before exit. The implementation is straightforward and includes a test to verify the change. I've added one comment suggesting a refactoring to improve the maintainability of an internal function that is accumulating a large number of parameters.

// initialization hooks (Python, runtime, package init), and finally calls
// main.main before returning 0.
func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, rtInit, abiInit llssa.Function) llssa.Function {
func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, pyFinalize, rtInit, abiInit llssa.Function) llssa.Function {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function now has 11 parameters, which can make it hard to read and maintain. Consider grouping the initialization and finalization functions into a struct to improve clarity.

For example:

type entryHooks struct {
    runtimeStub llssa.Function
    mainInit    llssa.Function
    mainMain    llssa.Function
    pyInit      llssa.Function
    pyFinalize  llssa.Function
    rtInit      llssa.Function
    abiInit     llssa.Function
}

func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, hooks *entryHooks) llssa.Function {
    // ...
    if hooks.pyInit != nil {
        b.Call(hooks.pyInit.Expr)
    }
    // ...
}

This would make the function signature more manageable and easier to extend in the future.

Comment on lines 108 to 110
// The entry stores argc/argv, optionally disables stdio buffering, runs
// initialization hooks (Python, runtime, package init), and finally calls
// main.main before returning 0.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment no longer reflects the full behavior — the function now also calls Py_Finalize() after main.main before returning 0.

Suggested change
// The entry stores argc/argv, optionally disables stdio buffering, runs
// initialization hooks (Python, runtime, package init), and finally calls
// main.main before returning 0.
// The entry stores argc/argv, optionally disables stdio buffering, runs
// initialization hooks (Python, runtime, package init), calls main.main,
// finalizes embedded Python if initialized, and returns 0.

Comment on lines 37 to 43
checks := []string{
"define i32 @main(",
"call void @Py_Initialize()",
"call void @Py_Finalize()",
"call void @\"example.com/foo.init\"()",
"define weak void @_start()",
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The test checks that each substring is present in the IR, but doesn't verify call ordering. An accidental swap (e.g., Py_Finalize before main.main) would still pass. Consider adding an index-based ordering check:

initIdx := strings.Index(ir, "call void @Py_Initialize()")
mainIdx := strings.Index(ir, `call void @"example.com/foo.main"()`)
finalIdx := strings.Index(ir, "call void @Py_Finalize()")
if !(initIdx < mainIdx && mainIdx < finalIdx) {
    t.Fatalf("incorrect call ordering: init@%d, main@%d, finalize@%d", initIdx, mainIdx, finalIdx)
}

@xgopilot
Copy link
Copy Markdown
Contributor

xgopilot bot commented Mar 12, 2026

Clean, well-scoped change. The Py_Finalize placement after main.main and before return 0 correctly mirrors the Py_Initialize at the top, and the nil-guard is consistent with the existing pattern for optional hooks. Note that Py_Finalize is naturally unreachable if the program exits via os.Exit or unrecovered panic — this matches Go's own semantics for cleanup and is not a defect. Left two minor inline suggestions.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.05%. Comparing base (8301bc3) to head (4369713).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1707      +/-   ##
==========================================
- Coverage   93.15%   93.05%   -0.11%     
==========================================
  Files          48       48              
  Lines       13331    13331              
==========================================
- Hits        12419    12405      -14     
- Misses        725      738      +13     
- Partials      187      188       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Python integration buffering issue

1 participant