Browse Source

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.
James Shubin 9 years ago
parent
commit
9aee3f01cd
4 changed files with 14 additions and 1 deletions
  1. 8 0
      embed/doc.go
  2. 2 1
      embed/etcd.go
  3. 1 0
      etcdmain/etcd.go
  4. 3 0
      integration/embed_test.go

+ 8 - 0
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())
 	}
 */

+ 2 - 1
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
 }
 

+ 1 - 0
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
 }
 

+ 3 - 0
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)