-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.go
More file actions
208 lines (179 loc) · 4.16 KB
/
parser.go
File metadata and controls
208 lines (179 loc) · 4.16 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package purejson
import (
"errors"
"fmt"
"os"
"runtime"
"sync"
"sync/atomic"
"github.com/amikos-tech/pure-simdjson/internal/ffi"
)
var (
abiVersionOverride atomic.Uint32
abiVersionOverrideSet atomic.Bool
parserFinalizerCount atomic.Int64
docFinalizerCount atomic.Int64
)
// Parser owns one live native parser handle and enforces a one-document-at-a-
// time lifecycle.
type Parser struct {
mu sync.Mutex
library *loadedLibrary
handle ffi.ParserHandle
closed bool
liveDoc ffi.DocHandle
}
// NewParser resolves the local shared library, verifies the ABI, and allocates
// a reusable native parser.
func NewParser() (*Parser, error) {
library, err := activeLibrary()
if err != nil {
return nil, err
}
actualABI, rc := library.bindings.ABI()
if err := wrapStatus(rc); err != nil {
return nil, err
}
expectedABI := expectedABIVersion()
if actualABI != expectedABI {
return nil, wrapABIMismatch(expectedABI, actualABI, library.path)
}
handle, rc := library.bindings.ParserNew()
if err := wrapStatus(rc); err != nil {
return nil, err
}
parser := &Parser{
library: library,
handle: handle,
}
attachParserFinalizer(parser)
return parser, nil
}
// Parse copies one JSON buffer into the native parser and returns a live Doc on
// success.
func (p *Parser) Parse(data []byte) (*Doc, error) {
p.mu.Lock()
defer p.mu.Unlock()
if p.library == nil {
return nil, ErrInvalidHandle
}
if p.closed {
return nil, ErrClosed
}
if p.liveDoc != 0 {
return nil, ErrParserBusy
}
handle := p.handle
library := p.library
docHandle, rc := library.bindings.ParserParse(handle, data)
runtime.KeepAlive(data)
runtime.KeepAlive(p)
if err := wrapParserStatus(library.bindings, handle, rc); err != nil {
return nil, err
}
root, rc := library.bindings.DocRoot(docHandle)
runtime.KeepAlive(p)
if err := wrapStatus(rc); err != nil {
if freeErr := wrapStatus(library.bindings.DocFree(docHandle)); freeErr != nil {
err = errors.Join(err, freeErr)
}
return nil, err
}
doc := &Doc{
parser: p,
handle: docHandle,
root: root,
}
attachDocFinalizer(doc)
p.liveDoc = docHandle
return doc, nil
}
// Close releases the native parser. While a live document still belongs to the
// parser, Close returns ErrParserBusy and leaves the parser usable. Subsequent
// calls after a successful Close return nil.
func (p *Parser) Close() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.library == nil {
return ErrInvalidHandle
}
if p.closed {
return nil
}
if p.liveDoc != 0 {
return ErrParserBusy
}
handle := p.handle
library := p.library
clearParserFinalizer(p)
rc := library.bindings.ParserFree(handle)
runtime.KeepAlive(p)
if err := wrapStatus(rc); err != nil {
attachParserFinalizer(p)
if leakWarningsEnabled() {
fmt.Fprintf(os.Stderr, "purejson close-failed: parser %v\n", err)
}
return err
}
p.closed = true
p.handle = 0
p.liveDoc = 0
return nil
}
func (p *Parser) clearLiveDoc(doc ffi.DocHandle) {
p.mu.Lock()
if p.liveDoc == doc {
p.liveDoc = 0
}
p.mu.Unlock()
}
func (p *Parser) hasLeakedState() bool {
p.mu.Lock()
defer p.mu.Unlock()
return !p.closed && (p.handle != 0 || p.liveDoc != 0)
}
// finalizeLeaked frees native resources from the GC finalizer. It releases the
// mutex around FFI calls so a stuck native side cannot hold the runtime, then
// re-acquires it to commit only the state for handles that actually freed.
func (p *Parser) finalizeLeaked() {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
return
}
handle := p.handle
liveDoc := p.liveDoc
library := p.library
p.mu.Unlock()
docFreed := liveDoc == 0
if liveDoc != 0 {
if rc := library.bindings.DocFree(liveDoc); rc == int32(ffi.OK) {
docFinalizerCount.Add(1)
docFreed = true
}
}
parserFreed := handle == 0
if handle != 0 {
if rc := library.bindings.ParserFree(handle); rc == int32(ffi.OK) {
parserFinalizerCount.Add(1)
parserFreed = true
}
}
p.mu.Lock()
if docFreed {
p.liveDoc = 0
}
if parserFreed {
p.handle = 0
}
if p.handle == 0 && p.liveDoc == 0 {
p.closed = true
}
p.mu.Unlock()
}
func expectedABIVersion() uint32 {
if abiVersionOverrideSet.Load() {
return abiVersionOverride.Load()
}
return ffi.ABIVersion
}