func (repo *postgresRepository) listenDaemon() error { doneCh := registry.DoneChannel() pqNotificationCh := repo.listener.NotificationChannel() for { select { case <-doneCh: return nil case pqNotification, ok := <-pqNotificationCh: if !ok { return nil } decodedChannel, err := hex.DecodeString(pqNotification.Channel[1:]) // trim leading '_' if err != nil { return err } decodedPayload, err := hex.DecodeString(pqNotification.Extra) if err != nil { return err } repo.m.RLock() for ch := range repo.listenChannels[string(decodedChannel)] { ch <- string(decodedPayload) } repo.m.RUnlock() } } }
func NewPostgresRepository(props properties.Properties) (Repository, error) { cs := kit.PostgresConnectionString(props) log.Printf("postgres connection string `%s`\n", cs) db, err := sql.Open("postgres", cs) if err != nil { return nil, err } repo := &postgresRepository{ connectionString: cs, db: db, listener: nil, listenChannels: map[string]map[chan string]struct{}{}, m: &sync.RWMutex{}, alive: true, } kit.SafeGo(func() error { <-registry.DoneChannel() repo.Close() return nil }) return repo, nil }
func (srv *service) Forever(setup func() error) error { log.Printf("start service [%v]\n", os.Getpid()) props := properties.Clone() log.Println("properties:") for _, k := range props.Keys() { log.Println(" ", k, props.Get(k)) } err := setup() if err != nil { return err } log.Println("service setup successful") sigCh := make(chan os.Signal) signal.Notify(sigCh, os.Kill, os.Interrupt, syscall.SIGTERM) err = nil select { case <-sigCh: case err = <-registry.ErrorChannel(): } close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() return err }
func (d *direct) Forever() { doneCh := registry.DoneChannel() var err error processed := true kit.SafeGo(func() error { for { if processed { select { case <-doneCh: return nil default: } processed, err = d.iter() if err != nil { return err } } else { select { case <-doneCh: return nil case <-d.resumeCh: processed, err = d.iter() if err != nil { return err } } } } }) }
func TestSanity(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() port := "44554" props := properties.Clone() props.Set("rest.port", port) handler := func(w http.ResponseWriter, r *http.Request) error { w.Write([]byte("done")) return nil } routes := PathHandlers{ "/abc": MethodHandlers{ "GET": handler, }, } err := NewServer(props, routes) if err != nil { t.Fatalf("new server error %v", err.Error()) } clientWG := sync.WaitGroup{} clientWG.Add(1) go func() { defer clientWG.Done() address := fmt.Sprintf("http://127.0.0.1:%v/abc", port) client := &http.Client{} req, err := http.NewRequest("GET", address, nil) if err != nil { t.Errorf("new request error %v", err.Error()) return } resp, err := client.Do(req) if err != nil { t.Fatalf("response err %v", err.Error()) } defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("response read all err %v", err.Error()) } if string(bytes) != "done" { t.Fatalf("invalid response %v", string(bytes)) } }() clientWG.Wait() close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
// NewServer construct rest server listening specific host and port and routes requests by passed routes func NewServer(props properties.Properties, routes PathHandlers) error { bind := props.Get("rest.address") + ":" + props.Get("rest.port") log.Println("start rest server on", bind) listener, err := net.Listen("tcp", bind) if err != nil { return err } s := &server{ listener: listener, m: &sync.Mutex{}, alive: true, wg: &sync.WaitGroup{}, } kit.SafeGo(func() error { router := mux.NewRouter() for path := range routes { mh := handlers.MethodHandler{} for method := range routes[path] { log.Printf("setup rest handler %v %v\n", method, path) mh[method] = s.wrapHandler(routes[path][method]) } router.Path(path).Handler(mh) } handler := http.Handler(router) compression, err := strconv.ParseBool(props.Get("rest.compression")) if err == nil && compression { log.Println("enable compression for rest server") handler = handlers.CompressHandler(handler) } else { log.Println("no compression for rest server") } log.Println("rest server serves") err = http.Serve(listener, handler) if err != nil { return err } return nil }) kit.SafeGo(func() error { <-registry.DoneChannel() s.Stop() return nil }) return nil }
func TestIdleStop(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() err := NewServer(properties.Clone(), PathHandlers{}) if err != nil { t.Fatalf("new server error %v", err.Error()) } close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
func TestDelayShutdownOnInitialDelay(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() scheduler := NewFixedDelayScheduler(100*time.Millisecond, 100*time.Millisecond) scheduler.Schedule(func() error { time.Sleep(100 * time.Millisecond) return nil }) <-time.After(40 * time.Millisecond) close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
func TestDelayTaskFailure(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() scheduler := NewFixedDelayScheduler(0, time.Second) scheduler.Schedule(func() error { return errors.New("asd") }) err := <-registry.ErrorChannel() close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() if err.Error() != "asd" { t.Errorf("unexpected error %v", err.Error()) } }
func NewFixedDelayScheduler(initialDelay time.Duration, delay time.Duration) Scheduler { scheduler := &fixedDelayScheduler{ initialDelay: initialDelay, delay: delay, alive: true, doneCh: make(chan struct{}), m: &sync.Mutex{}, wg: &sync.WaitGroup{}, } kit.SafeGo(func() error { <-registry.DoneChannel() scheduler.Stop() return nil }) return scheduler }
func TestDelaySanity(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() scheduler := NewFixedDelayScheduler(0, 100*time.Millisecond) counter := int32(0) scheduler.Schedule(func() error { atomic.AddInt32(&counter, 1) time.Sleep(100 * time.Millisecond) return nil }) <-time.After(300 * time.Millisecond) v := atomic.LoadInt32(&counter) if v != 2 { t.Errorf("invalid counter %v", v) } close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
func (d *direct) listen() error { doneCh := registry.DoneChannel() newEventChannel, err := d.repo.Listen(d.newEventChannelName) if err != nil { return err } for { select { case <-doneCh: return nil case _, ok := <-newEventChannel: if !ok { return nil } select { case d.resumeCh <- struct{}{}: default: } } } }
func TestDeflateCompression(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() port := "44559" props := properties.Clone() props.Set("rest.port", port) props.Set("rest.compression", "true") handler := func(w http.ResponseWriter, r *http.Request) error { w.Write([]byte("done")) return nil } routes := PathHandlers{ "/compression": MethodHandlers{ "GET": handler, }, } err := NewServer(props, routes) if err != nil { t.Fatalf("new server error %v", err.Error()) } clientWG := sync.WaitGroup{} clientWG.Add(1) go func() { defer clientWG.Done() address := fmt.Sprintf("http://127.0.0.1:%v/compression", port) client := &http.Client{} req, err := http.NewRequest("GET", address, nil) if err != nil { t.Errorf("new request error %v", err.Error()) return } req.Header.Add("Accept-Encoding", "deflate,gzip") resp, err := client.Do(req) if err != nil { t.Fatalf("response err %v", err.Error()) } defer resp.Body.Close() encoding := resp.Header.Get(http.CanonicalHeaderKey("Content-Encoding")) if encoding != "deflate" { t.Fatalf("invalid response encoding %v", encoding) } bytes, err := ioutil.ReadAll(flate.NewReader(resp.Body)) if err != nil { t.Fatalf("response read all err %v", err.Error()) } if string(bytes) != "done" { t.Fatalf("invalid response %v", string(bytes)) } }() clientWG.Wait() close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
func TestNilResponse(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() port := "44557" props := properties.Clone() props.Set("rest.port", port) handler := func(w http.ResponseWriter, r *http.Request) error { WriteResponse(w, nil, http.StatusConflict) return nil } routes := PathHandlers{ "/nil": MethodHandlers{ "GET": handler, }, } err := NewServer(props, routes) if err != nil { t.Fatalf("new server error %v", err.Error()) } clientWG := sync.WaitGroup{} clientWG.Add(1) go func() { defer clientWG.Done() address := fmt.Sprintf("http://127.0.0.1:%v/nil", port) client := &http.Client{} req, err := http.NewRequest("GET", address, nil) if err != nil { t.Fatalf("new request error %v", err.Error()) return } resp, err := client.Do(req) if err != nil { t.Fatalf("response err %v", err.Error()) } defer resp.Body.Close() if resp.StatusCode != http.StatusConflict { t.Fatalf("invalid response status code %v", resp.StatusCode) } var responseContent string err = json.NewDecoder(resp.Body).Decode(&responseContent) if err != nil { t.Fatalf("response decode error %v", err.Error()) } if responseContent != http.StatusText(http.StatusConflict) { t.Fatalf("invalid response %v", responseContent) } }() clientWG.Wait() close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }
func TestErrorInHandler(t *testing.T) { registry.DefaultServiceRegistry = registry.NewServiceRegistry() port := "44556" props := properties.Clone() props.Set("rest.port", port) errorMessage := "f951a2344b8349ac222f02b4646ad504efc759e9" handler := func(w http.ResponseWriter, r *http.Request) error { return errors.New(errorMessage) } routes := PathHandlers{ "/err": MethodHandlers{ "GET": handler, }, } err := NewServer(props, routes) if err != nil { t.Fatalf("new server error %v", err.Error()) } clientWG := sync.WaitGroup{} clientWG.Add(1) go func() { defer clientWG.Done() address := fmt.Sprintf("http://127.0.0.1:%v/err", port) client := &http.Client{} req, err := http.NewRequest("GET", address, nil) if err != nil { t.Fatalf("new request error %v", err.Error()) return } resp, err := client.Do(req) if err != nil { t.Fatalf("response err %v", err.Error()) } defer resp.Body.Close() if resp.StatusCode != http.StatusInternalServerError { t.Fatalf("invalid response status code %v", resp.StatusCode) } var responseContent string err = json.NewDecoder(resp.Body).Decode(&responseContent) if err != nil { t.Fatalf("response decode error %v", err.Error()) } if responseContent != errorMessage { t.Fatalf("invalid response %v", responseContent) } }() clientWG.Wait() close(registry.DoneChannel()) registry.DoneWaitGroup().Wait() }