@@ -4,21 +4,154 @@ import (
4
4
vorlageproc "ellem.so/vorlageproc"
5
5
"fmt"
6
6
"io"
7
+ "reflect"
7
8
"regexp"
9
+ "sync"
8
10
"sync/atomic"
9
11
)
10
12
11
13
var validProcessorName = regexp .MustCompile (`^[a-z0-9_\-]+$` )
12
14
15
+ // will also delete any processors in any list that have nil
16
+ func (c * Compiler ) rebuildProcessors () (err error ) {
17
+
18
+ // completely rebuild the processor list
19
+ newlist := make ([]vorlageproc.Processor , 0 , len (c .cprocessors )+ len (c .goprocessors ))
20
+ newlistinfo := make ([]vorlageproc.ProcessorInfo , 0 , len (c .cprocessors )+ len (c .goprocessors ))
21
+
22
+ // testarr will be a list of pointers to structures that implement vorlage.ProcessorInfo
23
+ var testarr = make ([]interface {}, 0 , len (newlist ))
24
+ addProc := func (arr interface {}) {
25
+ lenr := reflect .ValueOf (arr ).Len ()
26
+ for i := 0 ; i < lenr ; i ++ {
27
+ v := reflect .ValueOf (arr ).Index (i )
28
+ testarr = append (testarr , v .Interface ())
29
+ }
30
+ }
31
+
32
+ // arrptr must be a pointer to an array to pointers
33
+ removenulls := func (arrptr interface {}) {
34
+ arreml := reflect .ValueOf (arrptr ).Elem ()
35
+ lenr := arreml .Len ()
36
+ replacement := reflect .MakeSlice (arreml .Type (), 0 , lenr )
37
+ for i := 0 ; i < lenr ; i ++ {
38
+ v := arreml .Index (i )
39
+ if ! v .IsNil () {
40
+ replacement = reflect .Append (replacement , v )
41
+ }
42
+ }
43
+ t := replacement .Interface ()
44
+ a := arrptr
45
+ c := reflect .ValueOf (arrptr ).Elem ()
46
+ reflect .ValueOf (arrptr ).Elem ().Set (replacement )
47
+ _ = a
48
+ _ = c
49
+ _ = t
50
+ }
51
+
52
+ // remove null values to clean up
53
+ removenulls (& c .cprocessors )
54
+ removenulls (& c .goprocessors )
55
+
56
+ // copy each type of processor into testarr.
57
+ // clang / shared object
58
+ addProc (c .cprocessors )
59
+ addProc (c .goprocessors )
60
+
61
+ // find any processors that are no longer in testarr but remain in
62
+ // c.processors, those need to be removed.
63
+ for i := range c .processors {
64
+ var j int
65
+ for j = 0 ; j < len (testarr ); j ++ {
66
+ ptr := reflect .ValueOf (c .processors [i ]).Pointer ()
67
+ ptr2 := reflect .ValueOf (testarr [j ]).Pointer ()
68
+ if ptr == ptr2 {
69
+ // we still need this one
70
+ break
71
+ }
72
+ }
73
+ if j == len (testarr ) {
74
+ // this address in processors was not found in the upstream copies.
75
+ // thus this processor is no longer needed. Shut it down.
76
+ ptr := reflect .ValueOf (c .processors [i ]).Pointer ()
77
+ Logger .Alertf ("%s (@ %x) is no longer needed, shutting down" , c .processorInfos [i ].Name , ptr )
78
+ err = c .processors [i ].Shutdown ()
79
+ if err != nil {
80
+ Logger .Alertf ("error returned from shutdown.. this shouldn't happen as it will be ignored: %s" , err )
81
+ }
82
+ }
83
+ }
84
+
85
+ // now carry over old processors and add new ones.
86
+ for i := range testarr {
87
+ // is this processor loaed in c.processors?
88
+ var j int
89
+ for j = 0 ; j < len (c .processors ); j ++ {
90
+ ptr := reflect .ValueOf (c .processors [j ]).Pointer ()
91
+ ptr2 := reflect .ValueOf (testarr [i ]).Pointer ()
92
+ if ptr == ptr2 {
93
+ // yup. loaded already carrie it over
94
+ newlist = append (newlist , c .processors [j ])
95
+ newlistinfo = append (newlistinfo , c .processorInfos [j ])
96
+ break
97
+ }
98
+ }
99
+ if j == len (c .processors ) {
100
+ // this processor's address was not found in c.processors, put it in
101
+ newlist = append (newlist , testarr [i ].(vorlageproc.Processor ))
102
+ info , err := startupproc (newlist [len (newlist )- 1 ])
103
+ if err != nil {
104
+ return err
105
+ }
106
+ newlistinfo = append (newlistinfo , info )
107
+ }
108
+ }
109
+
110
+ c .processors = newlist
111
+ c .processorInfos = newlistinfo
112
+
113
+ return nil
114
+ }
115
+
13
116
// everything we'd see in both doccomp-http and doccomp-cli and doccomp-pdf
14
117
type Compiler struct {
15
118
119
+ // processors is technically a list of pointers to items found in cprocessors
120
+ // and goprocessors.
121
+ // If you want to change processors, you must update cprocessors / goprocessors
122
+ // and then run updateProcessors()...
16
123
// these two arrays are associative
17
124
processors []vorlageproc.Processor
18
125
processorInfos []vorlageproc.ProcessorInfo
19
126
20
- // used for thread safety of shutdown.
127
+ // if you change these, you need to run rebuildProcessors to take effect.
128
+ // if you set the pointers to nil, they will be marked for deletion.
129
+ // if you change the address of the pointer, they will be marked for reload.
130
+ cprocessors []* cProc
131
+ goprocessors []* goProc
132
+
133
+ // access these via the atomic.Load... funcitons
21
134
concurrentCompiles int64
135
+ concurrentReaders int32
136
+
137
+ // set to anything but 0 to have Compile reject requests.
138
+ // 1 = fully shutdown.
139
+ // 2 = shutting down but waiting for other compilers to close
140
+ // 3 = shutting down but waiting for other readers to close
141
+ // 4 = new compiles are being stalled due to a restart/reload of a processor
142
+ // the stall will continue until unstall is fed something
143
+ //
144
+ // shutdownCompilers0 and shutdownReaders0 will be listened to
145
+ // by shutdown when shutdown is in 2 and 3 states respectively. will be
146
+ // written too. If shutdown not in process, they will be nil
147
+ // todo: rename these.
148
+ atomicShutdown int32
149
+ shutdownCompilers0 chan bool
150
+ shutdownReaders0 chan bool
151
+ unstall sync.Mutex
152
+
153
+ // used for watching go reloads if AutoReloadGoFiles
154
+ gowatcher * watcher
22
155
}
23
156
24
157
type compileRequest struct {
@@ -81,28 +214,51 @@ func (c compileRequest) String() string {
81
214
return str
82
215
}
83
216
217
+ // see https://github.com/golang/go/issues/20461
218
+ var AutoReloadGoFiles bool = false
219
+
84
220
// will return an error if a processor failed to start and/or is invalid
85
- func NewCompiler (proc []vorlageproc.Processor ) (c Compiler , err error ) {
86
- c .processors = proc
221
+ func NewCompiler () (c * Compiler , err error ) {
87
222
88
- // load all the infos
89
- c .processorInfos = make ([]vorlageproc.ProcessorInfo , len (proc ))
90
- for i := range c .processors {
91
- c .processorInfos [i ], err = c .processors [i ].Startup ()
92
- Logger .Debugf ("starting %s..." , c .processorInfos [i ].Name )
93
- if err != nil {
94
- Logger .Alertf ("processor %s failed to start: %s" , c .processorInfos [i ].Name , err )
95
- return c , err
96
- }
97
- err = validate (& (c .processorInfos [i ]))
98
- if err != nil {
99
- return c , err
223
+ // structure set up
224
+ c = new (Compiler )
225
+
226
+ // load the go processors
227
+ c .goprocessors , err = loadGoProcessors (GoPluginLoadPath )
228
+ if err != nil {
229
+ return c , err
230
+ }
231
+ defer func () {
232
+ if AutoReloadGoFiles {
233
+ go c .watchGoPath (GoPluginLoadPath )
100
234
}
101
- Logger .Infof ("loaded processor %s" , c .processorInfos [i ].Name )
102
- Logger .Debugf ("%s information:\n %s" , c .processorInfos [i ].Name , c .processorInfos [i ])
235
+ }()
236
+
237
+ // load the c processors
238
+ c .cprocessors , err = loadCProcessors (CLoadPath )
239
+ if err != nil {
240
+ return c , err
103
241
}
104
242
105
- return c , nil
243
+ return c , c .rebuildProcessors ()
244
+ }
245
+
246
+ // helper to rebuildProcessors
247
+ func startupproc (proc vorlageproc.Processor ) (info vorlageproc.ProcessorInfo , err error ) {
248
+ ptr := reflect .ValueOf (proc ).Pointer ()
249
+ info , err = proc .Startup ()
250
+ Logger .Debugf ("starting %s (@ %x)..." , info .Name , ptr )
251
+ if err != nil {
252
+ Logger .Alertf ("processor %s (@ %x) failed to start: %s" , info .Name , ptr , err )
253
+ return info , err
254
+ }
255
+ err = validate (& (info ))
256
+ if err != nil {
257
+ return info , err
258
+ }
259
+ Logger .Infof ("successfully loaded processor %s (@ %x)" , info .Name , ptr )
260
+ Logger .Debugf ("%s information:\n %s" , info .Name , info )
261
+ return info , err
106
262
}
107
263
108
264
func validate (info * vorlageproc.ProcessorInfo ) error {
@@ -180,10 +336,42 @@ type ActionHandler interface {
180
336
* Do not attempt to use the streams pointed to by req... they'll be read
181
337
* when the docstream is read.
182
338
*/
183
- func (comp * Compiler ) Compile (filepath string , allInput map [string ]string , allStreams map [string ]vorlageproc.StreamInput , actionsHandler ActionHandler ) (docstream io.ReadCloser , err CompileStatus ) {
339
+ func (comp * Compiler ) Compile (filepath string , allInput map [string ]string ,
340
+ allStreams map [string ]vorlageproc.StreamInput , actionsHandler ActionHandler ) (docstream io.ReadCloser , err CompileStatus ) {
341
+
342
+ for i := range comp .processors {
343
+ Logger .Errorf ("%v" , comp .processors [i ])
344
+ }
345
+
346
+ if shutdowncode := atomic .LoadInt32 (& comp .atomicShutdown ); shutdowncode != 0 {
347
+ var erro error
348
+ switch shutdowncode {
349
+ case 1 :
350
+ erro = NewError ("compiler has shutdown" )
351
+ return nil , CompileStatus {erro , false }
352
+ case 2 :
353
+ erro = NewError ("compiler is shutting down, waiting on other compiliations to finish" )
354
+ return nil , CompileStatus {erro , false }
355
+ case 3 :
356
+ erro = NewError ("compiler is shutting down, waiting on other readers to finish" )
357
+ return nil , CompileStatus {erro , false }
358
+ case 4 :
359
+ // if 4, then we will try to lock unstall. which will lock this thread
360
+ // until Compiler.cont is called
361
+ comp .unstall .Lock ()
362
+ comp .unstall .Unlock ()
363
+ }
364
+ }
365
+
184
366
atomic .AddUint64 (& nextRid , 1 )
185
367
atomic .AddInt64 (& comp .concurrentCompiles , 1 )
186
- defer atomic .AddInt64 (& comp .concurrentCompiles , - 1 )
368
+ defer func () {
369
+ newi := atomic .AddInt64 (& comp .concurrentCompiles , - 1 )
370
+ shutdowncode := atomic .LoadInt32 (& comp .atomicShutdown )
371
+ if newi == 0 && shutdowncode == 2 {
372
+ comp .shutdownCompilers0 <- true
373
+ }
374
+ }()
187
375
compReq := compileRequest {
188
376
compiler : comp ,
189
377
filepath : filepath ,
@@ -266,27 +454,71 @@ func (comp *Compiler) Compile(filepath string, allInput map[string]string, allSt
266
454
return docstream , CompileStatus {erro , false }
267
455
}
268
456
269
- return & doc , CompileStatus {}
457
+ return doc , CompileStatus {}
270
458
}
271
459
272
460
/*
273
461
* Returns all errors that occour when shutting down each processor.
274
462
* If there is at least 1 Compile function that has not returned, Shutdown
275
463
* will return an error
276
464
*/
277
- func (comp * Compiler ) Shutdown () []error {
278
- compiles := atomic .LoadInt64 (& comp .concurrentCompiles )
279
- if compiles != 0 {
280
- erro := NewError ("compiles still running" )
281
- erro .SetSubjectf ("%d compile compRequest still processing" , compiles )
282
- return []error {erro }
465
+ func (comp * Compiler ) Shutdown () {
466
+ if comp .isshutdown () {
467
+ return
283
468
}
284
- var ret []error
469
+ if comp .gowatcher != nil {
470
+ comp .gowatcher .close ()
471
+ }
472
+ comp .makestall (1 )
473
+
474
+ // at this point, all readers and compilers are done.
285
475
for i := range comp .processors {
286
476
err := comp .processors [i ].Shutdown ()
287
477
if err != nil {
288
- ret = append ( ret , err )
478
+ Logger . Alertf ( "error returned from shutdown.. this shouldn't happen as it will be ignored: %s" , err )
289
479
}
290
480
}
291
- return ret
481
+ }
482
+
483
+ func (comp * Compiler ) isshutdown () bool {
484
+ return atomic .LoadInt32 (& comp .atomicShutdown ) == 1
485
+ }
486
+
487
+ // will wait until all readers and compiles on all threads are complete.
488
+ // if code is 1, will cause a full shutdown.
489
+ // if code is 4, will stall all Compile calls until cont is called. While stalled,
490
+ // you can make changes to the processor
491
+ func (comp * Compiler ) makestall (code int ) {
492
+ if comp .isshutdown () {
493
+ return
494
+ }
495
+ comp .shutdownCompilers0 = make (chan bool )
496
+ comp .shutdownReaders0 = make (chan bool )
497
+ if code == 4 {
498
+ Logger .Infof ("blocking compiles" )
499
+ comp .unstall .Lock ()
500
+ }
501
+ defer atomic .StoreInt32 (& comp .atomicShutdown , int32 (code ))
502
+
503
+ atomic .StoreInt32 (& comp .atomicShutdown , 2 )
504
+ compiles := atomic .LoadInt64 (& comp .concurrentCompiles )
505
+ if compiles != 0 {
506
+ Logger .Infof ("waiting for %d compiles to complete..." , compiles )
507
+ <- comp .shutdownCompilers0
508
+ }
509
+
510
+ atomic .StoreInt32 (& comp .atomicShutdown , 3 )
511
+ readers := atomic .LoadInt32 (& comp .concurrentReaders )
512
+ if readers != 0 {
513
+ Logger .Infof ("waiting for %d readers to close..." , readers )
514
+ <- comp .shutdownReaders0
515
+ }
516
+
517
+ }
518
+
519
+ // will undo the set-limbo state that makestall made
520
+ func (comp * Compiler ) cont () {
521
+ Logger .Infof ("unblocking compiles" )
522
+ atomic .StoreInt32 (& comp .atomicShutdown , 0 )
523
+ comp .unstall .Unlock ()
292
524
}
0 commit comments