@@ -13,6 +13,7 @@ import (
1313 "regexp"
1414 "strings"
1515 "sync"
16+ "time"
1617
1718 "github.com/mattn/go-shellwords"
1819 "github.com/superfly/litefs"
@@ -237,18 +238,32 @@ func (c *MountCommand) Run(ctx context.Context) (err error) {
237238 case <- c .Store .ReadyCh ():
238239 log .Printf ("connected to cluster, ready" )
239240 }
240- }
241241
242- // Execute subcommand, if specified in config.
243- if err := c .execCmd (ctx ); err != nil {
244- return fmt .Errorf ("cannot exec: %w" , err )
242+ // Automatically promote the server if requested & it is a candidate.
243+ if c .Config .Lease .Promote {
244+ if c .Store .Candidate () {
245+ log .Printf ("node is a candidate, automatically promoting to primary" )
246+ if err := c .promote (ctx ); err != nil {
247+ return fmt .Errorf ("promote: %w" , err )
248+ }
249+ } else {
250+ log .Printf ("node is not a candidate, skipping automatic promotion" )
251+ }
252+ }
245253 }
246254
255+ // Start the proxy server before the subcommand in case we need to hold
256+ // requests after we promote but before the server is ready.
247257 if c .ProxyServer != nil {
248258 c .ProxyServer .Serve ()
249259 log .Printf ("proxy server listening on: %s" , c .ProxyServer .URL ())
250260 }
251261
262+ // Execute subcommand, if specified in config.
263+ if err := c .execCmd (ctx ); err != nil {
264+ return fmt .Errorf ("cannot exec: %w" , err )
265+ }
266+
252267 return nil
253268}
254269
@@ -395,4 +410,35 @@ func (c *MountCommand) execCmd(ctx context.Context) error {
395410 return nil
396411}
397412
413+ // promote issues a lease handoff request to the current primary.
414+ func (c * MountCommand ) promote (ctx context.Context ) (err error ) {
415+ isPrimary , info := c .Store .PrimaryInfo ()
416+ if isPrimary {
417+ log .Printf ("node is already primary, skipping promotion" )
418+ return nil
419+ }
420+
421+ client := http .NewClient ()
422+ if err := client .Handoff (ctx , info .AdvertiseURL , c .Store .ID ()); err != nil {
423+ return fmt .Errorf ("handoff: %w" , err )
424+ }
425+
426+ // Wait for the local node to become primary.
427+ ticker := time .NewTicker (1 * time .Millisecond )
428+ defer ticker .Stop ()
429+ timeout := time .NewTicker (10 * time .Second )
430+ defer timeout .Stop ()
431+
432+ for {
433+ select {
434+ case <- timeout .C :
435+ return fmt .Errorf ("timed out waiting for promotion" )
436+ case <- ticker .C :
437+ if c .Store .IsPrimary () {
438+ return nil
439+ }
440+ }
441+ }
442+ }
443+
398444var expvarOnce sync.Once
0 commit comments