Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions stdlib/moduleinit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2026 The go-python Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package stdlib_test

import (
"sync"
"testing"

"github.com/go-python/gpython/py"
_ "github.com/go-python/gpython/stdlib"
)

// A registered ModuleImpl is shared by every Context, so importing a CodeSrc
// module from several contexts at once must not race (run with -race).
func TestModuleInitConcurrentCodeSrc(t *testing.T) {
py.RegisterModule(&py.ModuleImpl{
Info: py.ModuleInfo{
Name: "codesrc_test",
FileDesc: "<codesrc_test>",
},
CodeSrc: "X = 42\ndef get():\n return X\n",
})

const numContexts = 8
start := make(chan struct{})
var wg sync.WaitGroup
for i := 0; i < numContexts; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ctx := py.NewContext(py.DefaultContextOpts())
defer ctx.Close()
<-start
err := py.Import(ctx, "codesrc_test")
if err != nil {
t.Error(err)
return
}
module, err := ctx.GetModule("codesrc_test")
if err != nil {
t.Error(err)
return
}
res, err := module.Call("get", nil, nil)
if err != nil {
t.Error(err)
return
}
if res != py.Int(42) {
t.Errorf("get() = %v, want 42", res)
}
}()
}
close(start)
wg.Wait()
}
45 changes: 32 additions & 13 deletions stdlib/stdlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ func (ctx *context) ModuleInit(impl *py.ModuleImpl) (*py.Module, error) {
return nil, err
}

code, err := moduleCode(impl)
if err != nil {
return nil, err
}

module, err := ctx.Store().NewModule(ctx, impl)
if err != nil {
return nil, err
}

if code != nil {
_, err = ctx.RunCode(code, module.Globals, module.Globals, nil)
if err != nil {
return nil, err
}
}

return module, nil
}

// moduleCodeMu guards the compiling of ModuleImpl.CodeSrc or CodeBuf into
// ModuleImpl.Code, since a registered ModuleImpl is shared by every Context.
var moduleCodeMu sync.Mutex

// moduleCode returns impl.Code, setting it from impl.CodeSrc or impl.CodeBuf
// the first time it is needed.
func moduleCode(impl *py.ModuleImpl) (*py.Code, error) {
moduleCodeMu.Lock()
defer moduleCodeMu.Unlock()

var err error
if impl.Code == nil && len(impl.CodeSrc) > 0 {
impl.Code, err = py.Compile(string(impl.CodeSrc), impl.Info.FileDesc, py.ExecMode, 0, true)
if err != nil {
Expand All @@ -95,19 +126,7 @@ func (ctx *context) ModuleInit(impl *py.ModuleImpl) (*py.Module, error) {
}
}

module, err := ctx.Store().NewModule(ctx, impl)
if err != nil {
return nil, err
}

if impl.Code != nil {
_, err = ctx.RunCode(impl.Code, module.Globals, module.Globals, nil)
if err != nil {
return nil, err
}
}

return module, nil
return impl.Code, nil
}

// See interface py.Context defined in py/run.go
Expand Down
Loading