Skip to content

Commit 681abf2

Browse files
author
Drew O'Meara
committed
added Context.Close() and ModuleImpl.OnContextClosed()
1 parent 8033615 commit 681abf2

File tree

5 files changed

+163
-63
lines changed

5 files changed

+163
-63
lines changed

modules/runtime.go

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"path"
1212
"path/filepath"
1313
"strings"
14+
"sync"
1415

1516
"github.com/go-python/gpython/marshal"
1617
"github.com/go-python/gpython/py"
@@ -29,14 +30,22 @@ func init() {
2930

3031
// context implements py.Context
3132
type context struct {
32-
store *py.ModuleStore
33-
opts py.ContextOpts
33+
store *py.ModuleStore
34+
opts py.ContextOpts
35+
closeOnce sync.Once
36+
closing bool
37+
closed bool
38+
running sync.WaitGroup
39+
done chan struct{}
3440
}
3541

3642
// See py.Context interface
3743
func NewContext(opts py.ContextOpts) py.Context {
3844
ctx := &context{
39-
opts: opts,
45+
opts: opts,
46+
done: make(chan struct{}),
47+
closing: false,
48+
closed: false,
4049
}
4150

4251
ctx.store = py.NewModuleStore()
@@ -51,7 +60,11 @@ func NewContext(opts py.ContextOpts) py.Context {
5160
}
5261

5362
func (ctx *context) ModuleInit(impl *py.ModuleImpl) (*py.Module, error) {
54-
var err error
63+
err := ctx.PushBusy()
64+
defer ctx.PopBusy()
65+
if err != nil {
66+
return nil, err
67+
}
5568

5669
if impl.Code == nil && len(impl.CodeSrc) > 0 {
5770
impl.Code, err = py.Compile(string(impl.CodeSrc), impl.Info.FileDesc, py.ExecMode, 0, true)
@@ -72,7 +85,7 @@ func (ctx *context) ModuleInit(impl *py.ModuleImpl) (*py.Module, error) {
7285
}
7386
}
7487

75-
module, err := ctx.Store().NewModule(ctx, impl.Info, impl.Methods, impl.Globals)
88+
module, err := ctx.Store().NewModule(ctx, impl)
7689
if err != nil {
7790
return nil, err
7891
}
@@ -88,14 +101,20 @@ func (ctx *context) ModuleInit(impl *py.ModuleImpl) (*py.Module, error) {
88101
}
89102

90103
func (ctx *context) ResolveAndCompile(pathname string, opts py.CompileOpts) (py.CompileOut, error) {
104+
err := ctx.PushBusy()
105+
defer ctx.PopBusy()
106+
if err != nil {
107+
return py.CompileOut{}, err
108+
}
109+
91110
tryPaths := defaultPaths
92111
if opts.UseSysPaths {
93112
tryPaths = ctx.Store().MustGetModule("sys").Globals["path"].(*py.List).Items
94113
}
95114

96115
out := py.CompileOut{}
97116

98-
err := resolveRunPath(pathname, opts, tryPaths, func(fpath string) (bool, error) {
117+
err = resolveRunPath(pathname, opts, tryPaths, func(fpath string) (bool, error) {
99118

100119
stat, err := os.Stat(fpath)
101120
if err == nil && stat.IsDir() {
@@ -162,6 +181,36 @@ func (ctx *context) ResolveAndCompile(pathname string, opts py.CompileOpts) (py.
162181
return out, nil
163182
}
164183

184+
func (ctx *context) PushBusy() error {
185+
if ctx.closed {
186+
return py.ExceptionNewf(py.RuntimeError, "Context closed")
187+
}
188+
ctx.running.Add(1)
189+
return nil
190+
}
191+
192+
func (ctx *context) PopBusy() {
193+
ctx.running.Done()
194+
}
195+
196+
// Close -- see type py.Context
197+
func (ctx *context) Close() {
198+
ctx.closeOnce.Do(func() {
199+
ctx.closing = true
200+
ctx.running.Wait()
201+
ctx.closed = true
202+
203+
// Give each module a chance to release resources
204+
ctx.store.OnContextClosed()
205+
close(ctx.done)
206+
})
207+
}
208+
209+
// Done -- see type py.Context
210+
func (ctx *context) Done() <-chan struct{} {
211+
return ctx.done
212+
}
213+
165214
var defaultPaths = []py.Object{
166215
py.String("."),
167216
}
@@ -216,6 +265,12 @@ func resolveRunPath(runPath string, opts py.CompileOpts, pathObjs []py.Object, t
216265
}
217266

218267
func (ctx *context) RunCode(code *py.Code, globals, locals py.StringDict, closure py.Tuple) (py.Object, error) {
268+
err := ctx.PushBusy()
269+
defer ctx.PopBusy()
270+
if err != nil {
271+
return nil, err
272+
}
273+
219274
return vm.EvalCode(ctx, code, globals, locals, nil, nil, nil, nil, closure)
220275
}
221276

py/module.go

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import (
1414
type ModuleFlags int32
1515

1616
const (
17-
// Set for modules that are threadsafe, stateless, and/or can be shared across multiple py.Context instances (for efficiency).
18-
// Otherwise, a separate module instance is created for each py.Context that imports it.
19-
ShareModule ModuleFlags = 0x01 // @@TODO
17+
// ShareModule signals that an embedded module is threadsafe and read-only, meaninging it could be shared across multiple py.Context instances (for efficiency).
18+
// Otherwise, ModuleImpl will create a separate py.Module instance for each py.Context that imports it.
19+
// This should be used with extreme caution since any module mutation (write) means possible cross-context data corruption.
20+
ShareModule ModuleFlags = 0x01
2021

2122
MainModuleName = "__main__"
2223
)
@@ -34,12 +35,13 @@ type ModuleInfo struct {
3435
// By convention, .Code is executed when a module instance is initialized.
3536
// If .Code == nil, then .CodeBuf or .CodeSrc will be auto-compiled to set .Code.
3637
type ModuleImpl struct {
37-
Info ModuleInfo
38-
Methods []*Method
39-
Globals StringDict
40-
CodeSrc string // Module code body (py source code to be compiled)
41-
CodeBuf []byte // Module code body (serialized py.Code object)
42-
Code *Code // Module code body
38+
Info ModuleInfo
39+
Methods []*Method // Module-bound global method functions
40+
Globals StringDict // Module-bound global variables
41+
CodeSrc string // Module code body (source code to be compiled)
42+
CodeBuf []byte // Module code body (serialized py.Code object)
43+
Code *Code // Module code body
44+
OnContextClosed func(*Module) // Callback for when a py.Context is closing to release resources
4345
}
4446

4547
// ModuleStore is a container of Module imported into an owning py.Context.
@@ -87,10 +89,9 @@ func NewModuleStore() *ModuleStore {
8789

8890
// Module is a runtime instance of a ModuleImpl bound to the py.Context that imported it.
8991
type Module struct {
90-
ModuleInfo
91-
92-
Globals StringDict
93-
Context Context
92+
ModuleImpl *ModuleImpl // Parent implementation of this Module instance
93+
Globals StringDict // Initialized from ModuleImpl.Globals
94+
Context Context // Parent context that "owns" this Module instance
9495
}
9596

9697
var ModuleType = NewType("module", "module object")
@@ -101,51 +102,68 @@ func (o *Module) Type() *Type {
101102
}
102103

103104
func (m *Module) M__repr__() (Object, error) {
104-
return String(fmt.Sprintf("<module %s>", m.Name)), nil
105+
name, ok := m.Globals["__name__"].(String)
106+
if !ok {
107+
name = "???"
108+
}
109+
return String(fmt.Sprintf("<module %s>", string(name))), nil
105110
}
106111

107112
// Get the Dict
108113
func (m *Module) GetDict() StringDict {
109114
return m.Globals
110115
}
111116

117+
// Calls a named method of a module
118+
func (m *Module) Call(name string, args Tuple, kwargs StringDict) (Object, error) {
119+
attr, err := GetAttrString(m, name)
120+
if err != nil {
121+
return nil, err
122+
}
123+
return Call(attr, args, kwargs)
124+
}
125+
126+
// Interfaces
127+
var _ IGetDict = (*Module)(nil)
128+
112129
// NewModule adds a new Module instance to this ModuleStore.
113130
// Each given Method prototype is used to create a new "live" Method bound this the newly created Module.
114131
// This func also sets appropriate module global attribs based on the given ModuleInfo (e.g. __name__).
115-
func (store *ModuleStore) NewModule(ctx Context, info ModuleInfo, methods []*Method, globals StringDict) (*Module, error) {
116-
if info.Name == "" {
117-
info.Name = MainModuleName
132+
func (store *ModuleStore) NewModule(ctx Context, impl *ModuleImpl) (*Module, error) {
133+
name := impl.Info.Name
134+
if name == "" {
135+
name = MainModuleName
118136
}
119137
m := &Module{
120-
ModuleInfo: info,
121-
Globals: globals.Copy(),
138+
ModuleImpl: impl,
139+
Globals: impl.Globals.Copy(),
122140
Context: ctx,
123141
}
124142
// Insert the methods into the module dictionary
125143
// Copy each method an insert each "live" with a ptr back to the module (which can also lead us to the host Context)
126-
for _, method := range methods {
144+
for _, method := range impl.Methods {
127145
methodInst := new(Method)
128146
*methodInst = *method
129147
methodInst.Module = m
130148
m.Globals[method.Name] = methodInst
131149
}
132150
// Set some module globals
133-
m.Globals["__name__"] = String(info.Name)
134-
m.Globals["__doc__"] = String(info.Doc)
151+
m.Globals["__name__"] = String(name)
152+
m.Globals["__doc__"] = String(impl.Info.Doc)
135153
m.Globals["__package__"] = None
136-
if len(info.FileDesc) > 0 {
137-
m.Globals["__file__"] = String(info.FileDesc)
154+
if len(impl.Info.FileDesc) > 0 {
155+
m.Globals["__file__"] = String(impl.Info.FileDesc)
138156
}
139157
// Register the module
140-
store.modules[info.Name] = m
158+
store.modules[name] = m
141159
// Make a note of some modules
142-
switch info.Name {
160+
switch name {
143161
case "builtins":
144162
store.Builtins = m
145163
case "importlib":
146164
store.Importlib = m
147165
}
148-
// fmt.Printf("Registering module %q\n", name)
166+
// fmt.Printf("Registered module %q\n", moduleName)
149167
return m, nil
150168
}
151169

@@ -167,14 +185,11 @@ func (store *ModuleStore) MustGetModule(name string) *Module {
167185
return m
168186
}
169187

170-
// Calls a named method of a module
171-
func (m *Module) Call(name string, args Tuple, kwargs StringDict) (Object, error) {
172-
attr, err := GetAttrString(m, name)
173-
if err != nil {
174-
return nil, err
188+
// OnContextClosed signals all module instances that the parent py.Context has closed
189+
func (store *ModuleStore) OnContextClosed() {
190+
for _, m := range store.modules {
191+
if m.ModuleImpl.OnContextClosed != nil {
192+
m.ModuleImpl.OnContextClosed(m)
193+
}
175194
}
176-
return Call(attr, args, kwargs)
177195
}
178-
179-
// Interfaces
180-
var _ IGetDict = (*Module)(nil)

py/run.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,18 @@ type Context interface {
3333
// RunCode is a lower-level invocation to execute the given py.Code.
3434
RunCode(code *Code, globals, locals StringDict, closure Tuple) (result Object, err error)
3535

36-
// Execution of any of the above will stop when the next opcode runs
37-
// @@TODO
38-
// SignalHalt()
39-
4036
// Returns the named module for this context (or an error if not found)
4137
GetModule(moduleName string) (*Module, error)
4238

4339
// Gereric access to this context's modules / state.
4440
Store() *ModuleStore
41+
42+
// Close signals that is context is about to go out of scope and any internal resources should be released.
43+
// Operations on a py.Context that have closed will generally result in an error.
44+
Close()
45+
46+
// Done returns a signal that can be used to detect when this Context has fully closed / completed.
47+
Done() <-chan struct{}
4548
}
4649

4750
// CompileOpts specifies options for high-level compilation.
@@ -100,20 +103,45 @@ var (
100103
Compile func(src, srcDesc string, mode CompileMode, flags int, dont_inherit bool) (*Code, error)
101104
)
102105

103-
// RunFile resolves the given pathname, compiles as needed, and runs that code in the given module, returning the Module to indicate success.
104-
// If inModule is a *Module, then the code is run in that module.
105-
// If inModule is nil, the code is run in a new __main__ module (and the new Module is returned).
106-
// If inModule is a string, the code is run in a new module with the given name (and the new Module is returned).
106+
// RunFile resolves the given pathname, compiles as needed, executes the code in the given module, and returns the Module to indicate success.
107+
//
108+
// See RunCode() for description of inModule.
107109
func RunFile(ctx Context, pathname string, opts CompileOpts, inModule interface{}) (*Module, error) {
108110
out, err := ctx.ResolveAndCompile(pathname, opts)
109111
if err != nil {
110112
return nil, err
111113
}
112114

113-
var moduleName string
114-
createNew := false
115-
var module *Module
115+
return RunCode(ctx, out.Code, out.FileDesc, inModule)
116+
}
117+
118+
// RunSrc compiles the given python buffer and executes it within the given module and returns the Module to indicate success.
119+
//
120+
// See RunCode() for description of inModule.
121+
func RunSrc(ctx Context, pySrc string, pySrcDesc string, inModule interface{}) (*Module, error) {
122+
if pySrcDesc == "" {
123+
pySrcDesc = "<run>"
124+
}
125+
code, err := Compile(pySrc+"\n", pySrcDesc, SingleMode, 0, true)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return RunCode(ctx, code, pySrcDesc, inModule)
131+
}
116132

133+
// RunCode executes the given code object within the given module and returns the Module to indicate success.
134+
// If inModule is a *Module, then the code is run in that module.
135+
// If inModule is nil, the code is run in a new __main__ module (and the new Module is returned).
136+
// If inModule is a string, the code is run in a new module with the given name (and the new Module is returned).
137+
func RunCode(ctx Context, code *Code, codeDesc string, inModule interface{}) (*Module, error) {
138+
var (
139+
module *Module
140+
moduleName string
141+
err error
142+
)
143+
144+
createNew := false
117145
switch mod := inModule.(type) {
118146

119147
case string:
@@ -122,7 +150,7 @@ func RunFile(ctx Context, pathname string, opts CompileOpts, inModule interface{
122150
case nil:
123151
createNew = true
124152
case *Module:
125-
_, err = ctx.RunCode(out.Code, mod.Globals, mod.Globals, nil)
153+
_, err = ctx.RunCode(code, mod.Globals, mod.Globals, nil)
126154
module = mod
127155
default:
128156
err = ExceptionNewf(TypeError, "unsupported module type: %v", inModule)
@@ -132,9 +160,9 @@ func RunFile(ctx Context, pathname string, opts CompileOpts, inModule interface{
132160
moduleImpl := ModuleImpl{
133161
Info: ModuleInfo{
134162
Name: moduleName,
135-
FileDesc: out.FileDesc,
163+
FileDesc: codeDesc,
136164
},
137-
Code: out.Code,
165+
Code: code,
138166
}
139167
module, err = ctx.ModuleInit(&moduleImpl)
140168
}

py/util.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
"strconv"
1010
)
1111

12+
var (
13+
ErrUnsupportedObjType = errors.New("unsupported obj type")
14+
)
15+
1216
func GetLen(obj Object) (Int, error) {
1317
getlen, ok := obj.(I__len__)
1418
if !ok {
@@ -53,10 +57,6 @@ func LoadTuple(args Tuple, vars []interface{}) error {
5357
return nil
5458
}
5559

56-
var (
57-
ErrUnsupportedObjType = errors.New("unsupported obj type")
58-
)
59-
6060
func LoadAttr(obj Object, attrName string, data interface{}) error {
6161
attr, err := GetAttrString(obj, attrName)
6262
if err != nil {

0 commit comments

Comments
 (0)