1
1
package application
2
2
3
3
import (
4
+ "context"
4
5
"net/http"
5
6
"os"
7
+ "os/signal"
6
8
"path"
7
9
"path/filepath"
10
+ "syscall"
11
+ "time"
8
12
9
13
"github.com/docker/go-events"
10
14
"github.com/factorysh/microdensity/badge"
@@ -18,6 +22,7 @@ import (
18
22
"github.com/factorysh/microdensity/sessions"
19
23
"github.com/factorysh/microdensity/sink"
20
24
"github.com/factorysh/microdensity/storage"
25
+ "github.com/factorysh/microdensity/task"
21
26
"github.com/factorysh/microdensity/volumes"
22
27
"github.com/getsentry/sentry-go"
23
28
"github.com/go-chi/chi/v5"
@@ -39,6 +44,8 @@ type Application struct {
39
44
logger * zap.Logger
40
45
queue * queue.Queue
41
46
Sink events.Sink
47
+ Server * http.Server
48
+ Stopper chan (os.Signal )
42
49
}
43
50
44
51
func New (cfg * conf.Conf ) (* Application , error ) {
@@ -114,6 +121,7 @@ func New(cfg *conf.Conf) (*Application, error) {
114
121
logger : logger ,
115
122
queue : & q ,
116
123
Sink : & sink.VoidSink {},
124
+ Stopper : make (chan os.Signal , 1 ),
117
125
}
118
126
// A good base middleware stack
119
127
r .Use (middleware .RequestID )
@@ -202,3 +210,90 @@ func (a *Application) ListServices() []string {
202
210
203
211
return list
204
212
}
213
+
214
+ // Run make the app listen and serve requests
215
+ func (a * Application ) Run (listen string ) error {
216
+ // listen for stop/restart signals and sends them to the stopper channel
217
+ signal .Notify (a .Stopper , os .Interrupt , syscall .SIGINT , syscall .SIGTERM )
218
+
219
+ // setup the router
220
+ a .Server = & http.Server {
221
+ Addr : listen ,
222
+ Handler : a .Router ,
223
+ }
224
+
225
+ // interrupted tasks becomes ready task and are added to queue
226
+ tasks , err := a .storage .All ()
227
+ if err != nil {
228
+ return err
229
+ }
230
+
231
+ for _ , t := range tasks {
232
+ if t .State == task .Ready || t .State == task .Interrupted {
233
+ t .State = task .Ready
234
+ parsedArgs , err := a .Services [t .Service ].Validate (t .Args )
235
+ // non blocking error
236
+ if err != nil {
237
+ t .State = task .Failed
238
+ err := a .storage .Upsert (t )
239
+ if err != nil {
240
+ a .logger .Fatal ("unable to save task" , zap .Error (err ))
241
+ }
242
+
243
+ a .logger .Error ("error when validating task args" , zap .String ("task" , t .Id .String ()))
244
+ continue
245
+ }
246
+
247
+ err = a .addTask (t , parsedArgs .Environments )
248
+ // non blocking error
249
+ if err != nil {
250
+ a .logger .Error ("error when adding task" , zap .Error (err ))
251
+ }
252
+ }
253
+ }
254
+
255
+ // start and serve
256
+ go func () {
257
+ if err := a .Server .ListenAndServe (); err != nil && err != http .ErrServerClosed {
258
+ a .logger .Fatal ("fatal error when running server" , zap .Error (err ))
259
+ }
260
+ }()
261
+
262
+ return nil
263
+ }
264
+
265
+ // Shutdown the server and put running tasks into interrupted state
266
+ func (a * Application ) Shutdown () error {
267
+ ctx , cancel := context .WithTimeout (context .Background (), 3 * time .Second )
268
+ defer cancel ()
269
+
270
+ // handle the last pending requests
271
+ // we don't want this error stop the shutdown flow
272
+ // just log it
273
+ err := a .Server .Shutdown (ctx )
274
+ if err != nil {
275
+ a .logger .Error ("error on server shutdown" , zap .Error (err ))
276
+ }
277
+
278
+ tasks , err := a .storage .All ()
279
+ if err != nil {
280
+ return err
281
+ }
282
+
283
+ for _ , t := range tasks {
284
+ // running tasks becomes interrupted tasks
285
+ if t .State == task .Running {
286
+ // TODO: send a cancel request to docker ?
287
+ t .State = task .Interrupted
288
+ err := a .storage .Upsert (t )
289
+ // same here, non blocking error
290
+ if err != nil {
291
+ a .logger .Error ("error when updating task status" , zap .Error (err ), zap .String ("task id" , t .Id .String ()))
292
+ }
293
+ }
294
+ }
295
+
296
+ a .logger .Info ("server shutdown" )
297
+
298
+ return nil
299
+ }
0 commit comments