-
-
Notifications
You must be signed in to change notification settings - Fork 320
Expand file tree
/
Copy pathsession.go
More file actions
118 lines (97 loc) · 3.35 KB
/
session.go
File metadata and controls
118 lines (97 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package ferret
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/MontFerret/ferret/v2/pkg/encoding"
"github.com/MontFerret/ferret/v2/pkg/fs"
"github.com/MontFerret/ferret/v2/pkg/logging"
"github.com/MontFerret/ferret/v2/pkg/runtime"
"github.com/MontFerret/ferret/v2/pkg/vm"
)
type (
vmReleaseFunc func(*vm.VM)
// Session represents the execution of a compiled Ferret program.
// It holds the state of the execution, including the virtual machine, environment, and encoding registry.
// A Session is created from a Plan and can be run to obtain results.
//
// Session is not safe for concurrent use by multiple goroutines, except that
// Close is idempotent and safe to call multiple times, including concurrently.
// It is typically used for a single logical execution. When a Session is created
// directly via Plan.NewSession, it may be reused for multiple sequential runs as
// long as the environment and encoding registry are not modified between runs.
// Helper APIs such as Engine.Run may take ownership of the Session and close it
// after a single execution, in which case the caller must not attempt to reuse it.
Session struct {
hooks sessionHooks
closeErr error
vm *vm.VM
env *vm.Environment
encoding *encoding.Registry
logger logging.Logger
fs fs.FileSystem
release vmReleaseFunc
outputContentType string
closeOnce sync.Once
closed atomic.Bool
}
)
// Run executes the session with the provided context and returns encoded output.
func (s *Session) Run(c context.Context) (*Output, error) {
if s.closed.Load() {
return nil, runtime.Error(runtime.ErrInvalidOperation, "session is closed")
}
// Before-run hooks can replace the context used for the rest of execution.
ctx, err := s.hooks.runBeforeRunHooks(c)
if err != nil {
return nil, fmt.Errorf("before run hooks: %w", err)
}
out, err := s.vm.Run(s.extendContext(ctx), s.env)
// After-run hooks always run and receive the VM run error (if any).
if hookErr := s.hooks.runAfterRunHooks(ctx, err); hookErr != nil {
return nil, errors.Join(err, fmt.Errorf("after run hooks: %w", hookErr))
}
if err != nil {
return nil, err
}
output, outputErr := newOutput(s.encoding, s.outputContentType, out)
closeErr := out.Close()
if outputErr != nil {
return nil, errors.Join(outputErr, closeErr)
}
if closeErr != nil {
return output, closeErr
}
return output, nil
}
func (s *Session) extendContext(ctx context.Context) context.Context {
ctx = s.logger.WithContext(ctx)
ctx = encoding.WithRegistry(ctx, s.encoding)
ctx = fs.WithFileSystem(ctx, s.fs)
return ctx
}
// Close releases the session's borrowed VM and runs close hooks.
// It is idempotent and safe to call multiple times, including concurrently.
func (s *Session) Close() error {
if s == nil {
return nil
}
s.closeOnce.Do(func() {
s.closed.Store(true)
instance := s.vm
release := s.release
s.vm = nil
s.release = nil
if hookErr := s.hooks.runCloseHooks(); hookErr != nil {
s.closeErr = fmt.Errorf("close hooks: %w", hookErr)
}
if release != nil && instance != nil {
// Returning the borrowed VM is best-effort cleanup and must still happen when
// close hooks fail so the pool/limiter do not leak capacity.
release(instance)
}
})
return s.closeErr
}