@@ -68,6 +68,7 @@ type daemonControlOptions struct {
6868 StatePath string
6969 Status bool
7070 Stop bool
71+ Tick bool
7172}
7273
7374type repeatedStringFlag []string
@@ -93,6 +94,7 @@ func run(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int
9394 daemonHeartbeat := flags .Duration ("daemon-heartbeat" , 5 * time .Second , "daemon heartbeat interval" )
9495 daemonStatus := flags .Bool ("daemon-status" , false , "print daemon status and exit" )
9596 daemonStop := flags .Bool ("daemon-stop" , false , "stop the daemon recorded in daemon state" )
97+ daemonTick := flags .Bool ("daemon-tick" , false , "trigger one daemon schedule tick" )
9698 daemonStatePath := flags .String ("daemon-state" , "" , "daemon state path" )
9799 cwd := flags .String ("cwd" , "" , "working directory" )
98100 printMode := flags .Bool ("print" , false , "print response and exit" )
@@ -152,11 +154,12 @@ func run(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int
152154 fmt .Fprintf (stderr , "ccgo: %v\n " , err )
153155 return 1
154156 }
155- if * daemonStatus || * daemonStop {
157+ if * daemonStatus || * daemonStop || * daemonTick {
156158 return runDaemonControl (context .Background (), state , daemonControlOptions {
157159 StatePath : strings .TrimSpace (* daemonStatePath ),
158160 Status : * daemonStatus ,
159161 Stop : * daemonStop ,
162+ Tick : * daemonTick ,
160163 }, stdout , stderr )
161164 }
162165 if * daemonMode || * daemonOnce {
@@ -263,8 +266,14 @@ func runChromeNativeHost(stdin io.Reader, stdout io.Writer, stderr io.Writer) in
263266}
264267
265268func runDaemonControl (ctx context.Context , state * bootstrap.State , options daemonControlOptions , stdout io.Writer , stderr io.Writer ) int {
266- if options .Status && options .Stop {
267- fmt .Fprintf (stderr , "ccgo daemon: --daemon-status and --daemon-stop are mutually exclusive\n " )
269+ actionCount := 0
270+ for _ , enabled := range []bool {options .Status , options .Stop , options .Tick } {
271+ if enabled {
272+ actionCount ++
273+ }
274+ }
275+ if actionCount > 1 {
276+ fmt .Fprintf (stderr , "ccgo daemon: daemon control actions are mutually exclusive\n " )
268277 return 2
269278 }
270279 statePath , err := resolveDaemonStatePath (state , options .StatePath )
@@ -279,6 +288,13 @@ func runDaemonControl(ctx context.Context, state *bootstrap.State, options daemo
279288 }
280289 return 0
281290 }
291+ if options .Tick {
292+ if err := tickDaemon (ctx , stdout , statePath ); err != nil {
293+ fmt .Fprintf (stderr , "ccgo daemon: %v\n " , err )
294+ return 1
295+ }
296+ return 0
297+ }
282298 if options .Stop {
283299 if err := stopDaemon (ctx , stdout , statePath ); err != nil {
284300 fmt .Fprintf (stderr , "ccgo daemon: %v\n " , err )
@@ -383,6 +399,39 @@ func stopDaemon(ctx context.Context, stdout io.Writer, statePath string) error {
383399 return err
384400}
385401
402+ func tickDaemon (ctx context.Context , stdout io.Writer , statePath string ) error {
403+ state , err := daemonpkg .LoadState (statePath )
404+ if err != nil {
405+ return err
406+ }
407+ if state .GeneratedAt == "" {
408+ return fmt .Errorf ("daemon state not found: %s" , statePath )
409+ }
410+ if strings .TrimSpace (state .Endpoint ) == "" {
411+ return fmt .Errorf ("daemon endpoint is unavailable in %s" , statePath )
412+ }
413+ ctx , cancel := context .WithTimeout (ctx , 2 * time .Second )
414+ defer cancel ()
415+ response , err := postDaemonTick (ctx , state .Endpoint )
416+ if err != nil {
417+ return err
418+ }
419+ lines := []string {
420+ "ccgo daemon tick" ,
421+ "state_path=" + statePath ,
422+ "ok=true" ,
423+ }
424+ if response .CheckedAt != "" {
425+ lines = append (lines , "checked_at=" + response .CheckedAt )
426+ }
427+ lines = append (lines ,
428+ fmt .Sprintf ("triggered_count=%d" , response .TriggeredCount ),
429+ fmt .Sprintf ("error_count=%d" , response .ErrorCount ),
430+ )
431+ _ , err = fmt .Fprintln (stdout , strings .Join (lines , "\n " ))
432+ return err
433+ }
434+
386435func postDaemonStop (ctx context.Context , endpoint string ) (daemonpkg.StopResponse , error ) {
387436 req , err := http .NewRequestWithContext (ctx , http .MethodPost , strings .TrimRight (endpoint , "/" )+ "/stop" , nil )
388437 if err != nil {
@@ -406,6 +455,29 @@ func postDaemonStop(ctx context.Context, endpoint string) (daemonpkg.StopRespons
406455 return response , nil
407456}
408457
458+ func postDaemonTick (ctx context.Context , endpoint string ) (daemonpkg.TickResponse , error ) {
459+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , strings .TrimRight (endpoint , "/" )+ "/tick" , nil )
460+ if err != nil {
461+ return daemonpkg.TickResponse {}, err
462+ }
463+ resp , err := http .DefaultClient .Do (req )
464+ if err != nil {
465+ return daemonpkg.TickResponse {}, err
466+ }
467+ defer resp .Body .Close ()
468+ var response daemonpkg.TickResponse
469+ if err := json .NewDecoder (resp .Body ).Decode (& response ); err != nil {
470+ return daemonpkg.TickResponse {}, err
471+ }
472+ if resp .StatusCode < 200 || resp .StatusCode > 299 || ! response .OK {
473+ if response .Error == "" {
474+ response .Error = resp .Status
475+ }
476+ return daemonpkg.TickResponse {}, errors .New (response .Error )
477+ }
478+ return response , nil
479+ }
480+
409481func waitForDaemonRuntimeState (ctx context.Context , statePath string , runtimeState string ) daemonpkg.State {
410482 deadline := time .NewTimer (time .Second )
411483 defer deadline .Stop ()
0 commit comments