From 9aee3f01cd5d477d4ebf35f68aebae9836b04d4a Mon Sep 17 00:00:00 2001 From: James Shubin Date: Mon, 29 Aug 2016 02:04:06 -0400 Subject: [PATCH] embed: Move the ReadyNotify() call to a better place When using the embed functionality, you can't call the Server.Stop() function until StartEtcd returns, which can block until there is a call to Server.Stop() in error situations. Since we have a catch-22, the ReadyNotify() can be called manually by the user if they wish to wait for the server startup, or in parallel with a timeout if they wish to cancel it after some time. Chzz pointed out that this is also more consistent with the etcdserver.Start() behaviour too. purpleidea pointed out that this is actually more correct too, because we can now register the stop interrupt handler before we block on startup. --- embed/doc.go | 8 ++++++++ embed/etcd.go | 3 ++- etcdmain/etcd.go | 1 + integration/embed_test.go | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/embed/doc.go b/embed/doc.go index 362fee9bc..c555aa58e 100644 --- a/embed/doc.go +++ b/embed/doc.go @@ -19,6 +19,7 @@ Launch an embedded etcd server using the configuration defaults: import ( "log" + "time" "github.com/coreos/etcd/embed" ) @@ -31,6 +32,13 @@ Launch an embedded etcd server using the configuration defaults: log.Fatal(err) } defer e.Close() + select { + case <-e.Server.ReadyNotify(): + log.Printf("Server is ready!") + case <-time.After(60 * time.Second): + e.Server.Stop() // trigger a shutdown + log.Printf("Server took too long to start!") + } log.Fatal(<-e.Err()) } */ diff --git a/embed/etcd.go b/embed/etcd.go index 4f62e72df..5ffcbf1a5 100644 --- a/embed/etcd.go +++ b/embed/etcd.go @@ -59,6 +59,8 @@ type Etcd struct { } // StartEtcd launches the etcd server and HTTP handlers for client/server communication. +// The returned Etcd.Server is not guaranteed to have joined the cluster. Wait +// on the Etcd.Server.ReadyNotify() channel to know when it completes and is ready for use. func StartEtcd(inCfg *Config) (e *Etcd, err error) { if err = inCfg.Validate(); err != nil { return nil, err @@ -130,7 +132,6 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) { if err = e.serve(); err != nil { return } - <-e.Server.ReadyNotify() return } diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go index f36a9a6e2..9b03d3e3c 100644 --- a/etcdmain/etcd.go +++ b/etcdmain/etcd.go @@ -186,6 +186,7 @@ func startEtcd(cfg *embed.Config) (<-chan struct{}, <-chan error, error) { return nil, nil, err } osutil.RegisterInterruptHandler(e.Server.Stop) + <-e.Server.ReadyNotify() // wait for e.Server to join the cluster return e.Server.StopNotify(), e.Err(), nil } diff --git a/integration/embed_test.go b/integration/embed_test.go index 1a8075bc5..4b27973fc 100644 --- a/integration/embed_test.go +++ b/integration/embed_test.go @@ -65,6 +65,9 @@ func TestEmbedEtcd(t *testing.T) { for i, tt := range tests { tests[i].cfg.Dir = dir e, err := embed.StartEtcd(&tests[i].cfg) + if e != nil { + <-e.Server.ReadyNotify() // wait for e.Server to join the cluster + } if tt.werr != "" { if err == nil || !strings.Contains(err.Error(), tt.werr) { t.Errorf("%d: expected error with %q, got %v", i, tt.werr, err)