
The big change is that the GeoJSON package has been completely rewritten to fix a few of geometry calculation bugs, increase performance, and to better follow the GeoJSON spec RFC 7946. GeoJSON updates - A LineString now requires at least two points. - All json members, even foreign, now persist with the object. - The bbox member persists too but is no longer used for geometry calculations. This is change in behavior. Previously Tile38 would treat the bbox as the object's physical rectangle. - Corrections to geometry intersects and within calculations. Faster spatial queries - The performance of Point-in-polygon and object intersect operations are greatly improved for complex polygons and line strings. It went from O(n) to roughly O(log n). - The same for all collection types with many children, including FeatureCollection, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon. Codebase changes - The pkg directory has been renamed to internal - The GeoJSON internal package has been moved to a seperate repo at https://github.com/tidwall/geojson. It's now vendored. Please look out for higher memory usage for datasets using complex shapes. A complex shape is one that has 64 or more points. For these shapes it's expected that there will be increase of least 54 bytes per point.
652 lines
15 KiB
Go
652 lines
15 KiB
Go
// Copyright 2013-2018 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package test
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/gnatsd/server"
|
|
"github.com/nats-io/go-nats"
|
|
)
|
|
|
|
func startReconnectServer(t *testing.T) *server.Server {
|
|
return RunServerOnPort(22222)
|
|
}
|
|
|
|
func TestReconnectTotalTime(t *testing.T) {
|
|
opts := nats.GetDefaultOptions()
|
|
totalReconnectTime := time.Duration(opts.MaxReconnect) * opts.ReconnectWait
|
|
if totalReconnectTime < (2 * time.Minute) {
|
|
t.Fatalf("Total reconnect time should be at least 2 mins: Currently %v\n",
|
|
totalReconnectTime)
|
|
}
|
|
}
|
|
|
|
func TestReconnectDisallowedFlags(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
ch := make(chan bool)
|
|
opts := nats.GetDefaultOptions()
|
|
opts.Url = "nats://localhost:22222"
|
|
opts.AllowReconnect = false
|
|
opts.ClosedCB = func(_ *nats.Conn) {
|
|
ch <- true
|
|
}
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ts.Shutdown()
|
|
|
|
if e := Wait(ch); e != nil {
|
|
t.Fatal("Did not trigger ClosedCB correctly")
|
|
}
|
|
}
|
|
|
|
func TestReconnectAllowedFlags(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
ch := make(chan bool)
|
|
dch := make(chan bool)
|
|
opts := nats.GetDefaultOptions()
|
|
opts.Url = "nats://localhost:22222"
|
|
opts.AllowReconnect = true
|
|
opts.MaxReconnect = 2
|
|
opts.ReconnectWait = 1 * time.Second
|
|
|
|
opts.ClosedCB = func(_ *nats.Conn) {
|
|
ch <- true
|
|
}
|
|
opts.DisconnectedCB = func(_ *nats.Conn) {
|
|
dch <- true
|
|
}
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ts.Shutdown()
|
|
|
|
// We want wait to timeout here, and the connection
|
|
// should not trigger the Close CB.
|
|
if e := WaitTime(ch, 500*time.Millisecond); e == nil {
|
|
t.Fatal("Triggered ClosedCB incorrectly")
|
|
}
|
|
|
|
// We should wait to get the disconnected callback to ensure
|
|
// that we are in the process of reconnecting.
|
|
if e := Wait(dch); e != nil {
|
|
t.Fatal("DisconnectedCB should have been triggered")
|
|
}
|
|
|
|
if !nc.IsReconnecting() {
|
|
t.Fatal("Expected to be in a reconnecting state")
|
|
}
|
|
|
|
// clear the CloseCB since ch will block
|
|
nc.Opts.ClosedCB = nil
|
|
}
|
|
|
|
var reconnectOpts = nats.Options{
|
|
Url: "nats://localhost:22222",
|
|
AllowReconnect: true,
|
|
MaxReconnect: 10,
|
|
ReconnectWait: 100 * time.Millisecond,
|
|
Timeout: nats.DefaultTimeout,
|
|
}
|
|
|
|
func TestConnCloseBreaksReconnectLoop(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
cch := make(chan bool)
|
|
|
|
opts := reconnectOpts
|
|
// Bump the max reconnect attempts
|
|
opts.MaxReconnect = 100
|
|
opts.ClosedCB = func(_ *nats.Conn) {
|
|
cch <- true
|
|
}
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
nc.Flush()
|
|
|
|
// Shutdown the server
|
|
ts.Shutdown()
|
|
|
|
// Wait a second, then close the connection
|
|
time.Sleep(time.Second)
|
|
|
|
// Close the connection, this should break the reconnect loop.
|
|
// Do this in a go routine since the issue was that Close()
|
|
// would block until the reconnect loop is done.
|
|
go nc.Close()
|
|
|
|
// Even on Windows (where a createConn takes more than a second)
|
|
// we should be able to break the reconnect loop with the following
|
|
// timeout.
|
|
if err := WaitTime(cch, 3*time.Second); err != nil {
|
|
t.Fatal("Did not get a closed callback")
|
|
}
|
|
}
|
|
|
|
func TestBasicReconnectFunctionality(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
ch := make(chan bool)
|
|
dch := make(chan bool)
|
|
|
|
opts := reconnectOpts
|
|
|
|
opts.DisconnectedCB = func(_ *nats.Conn) {
|
|
dch <- true
|
|
}
|
|
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v\n", err)
|
|
}
|
|
defer nc.Close()
|
|
ec, err := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create an encoded connection: %v\n", err)
|
|
}
|
|
|
|
testString := "bar"
|
|
ec.Subscribe("foo", func(s string) {
|
|
if s != testString {
|
|
t.Fatal("String doesn't match")
|
|
}
|
|
ch <- true
|
|
})
|
|
ec.Flush()
|
|
|
|
ts.Shutdown()
|
|
// server is stopped here...
|
|
|
|
if err := Wait(dch); err != nil {
|
|
t.Fatalf("Did not get the disconnected callback on time\n")
|
|
}
|
|
|
|
if err := ec.Publish("foo", testString); err != nil {
|
|
t.Fatalf("Failed to publish message: %v\n", err)
|
|
}
|
|
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
if err := ec.FlushTimeout(5 * time.Second); err != nil {
|
|
t.Fatalf("Error on Flush: %v", err)
|
|
}
|
|
|
|
if e := Wait(ch); e != nil {
|
|
t.Fatal("Did not receive our message")
|
|
}
|
|
|
|
expectedReconnectCount := uint64(1)
|
|
reconnectCount := ec.Conn.Stats().Reconnects
|
|
|
|
if reconnectCount != expectedReconnectCount {
|
|
t.Fatalf("Reconnect count incorrect: %d vs %d\n",
|
|
reconnectCount, expectedReconnectCount)
|
|
}
|
|
}
|
|
|
|
func TestExtendedReconnectFunctionality(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
opts := reconnectOpts
|
|
dch := make(chan bool)
|
|
opts.DisconnectedCB = func(_ *nats.Conn) {
|
|
dch <- true
|
|
}
|
|
rch := make(chan bool)
|
|
opts.ReconnectedCB = func(_ *nats.Conn) {
|
|
rch <- true
|
|
}
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
ec, err := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create an encoded connection: %v\n", err)
|
|
}
|
|
testString := "bar"
|
|
received := int32(0)
|
|
|
|
ec.Subscribe("foo", func(s string) {
|
|
atomic.AddInt32(&received, 1)
|
|
})
|
|
|
|
sub, _ := ec.Subscribe("foobar", func(s string) {
|
|
atomic.AddInt32(&received, 1)
|
|
})
|
|
|
|
ec.Publish("foo", testString)
|
|
ec.Flush()
|
|
|
|
ts.Shutdown()
|
|
// server is stopped here..
|
|
|
|
// wait for disconnect
|
|
if e := WaitTime(dch, 2*time.Second); e != nil {
|
|
t.Fatal("Did not receive a disconnect callback message")
|
|
}
|
|
|
|
// Sub while disconnected
|
|
ec.Subscribe("bar", func(s string) {
|
|
atomic.AddInt32(&received, 1)
|
|
})
|
|
|
|
// Unsub foobar while disconnected
|
|
sub.Unsubscribe()
|
|
|
|
if err = ec.Publish("foo", testString); err != nil {
|
|
t.Fatalf("Received an error after disconnect: %v\n", err)
|
|
}
|
|
|
|
if err = ec.Publish("bar", testString); err != nil {
|
|
t.Fatalf("Received an error after disconnect: %v\n", err)
|
|
}
|
|
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
// server is restarted here..
|
|
// wait for reconnect
|
|
if e := WaitTime(rch, 2*time.Second); e != nil {
|
|
t.Fatal("Did not receive a reconnect callback message")
|
|
}
|
|
|
|
if err = ec.Publish("foobar", testString); err != nil {
|
|
t.Fatalf("Received an error after server restarted: %v\n", err)
|
|
}
|
|
|
|
if err = ec.Publish("foo", testString); err != nil {
|
|
t.Fatalf("Received an error after server restarted: %v\n", err)
|
|
}
|
|
|
|
ch := make(chan bool)
|
|
ec.Subscribe("done", func(b bool) {
|
|
ch <- true
|
|
})
|
|
ec.Publish("done", true)
|
|
|
|
if e := Wait(ch); e != nil {
|
|
t.Fatal("Did not receive our message")
|
|
}
|
|
|
|
// Sleep a bit to guarantee scheduler runs and process all subs.
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
if atomic.LoadInt32(&received) != 4 {
|
|
t.Fatalf("Received != %d, equals %d\n", 4, received)
|
|
}
|
|
}
|
|
|
|
func TestQueueSubsOnReconnect(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
opts := reconnectOpts
|
|
|
|
// Allow us to block on reconnect complete.
|
|
reconnectsDone := make(chan bool)
|
|
opts.ReconnectedCB = func(nc *nats.Conn) {
|
|
reconnectsDone <- true
|
|
}
|
|
|
|
// Create connection
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v\n", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
ec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create an encoded connection: %v\n", err)
|
|
}
|
|
|
|
// To hold results.
|
|
results := make(map[int]int)
|
|
var mu sync.Mutex
|
|
|
|
// Make sure we got what we needed, 1 msg only and all seqnos accounted for..
|
|
checkResults := func(numSent int) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
for i := 0; i < numSent; i++ {
|
|
if results[i] != 1 {
|
|
t.Fatalf("Received incorrect number of messages, [%d] for seq: %d\n", results[i], i)
|
|
}
|
|
}
|
|
|
|
// Auto reset results map
|
|
results = make(map[int]int)
|
|
}
|
|
|
|
subj := "foo.bar"
|
|
qgroup := "workers"
|
|
|
|
cb := func(seqno int) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
results[seqno] = results[seqno] + 1
|
|
}
|
|
|
|
// Create Queue Subscribers
|
|
ec.QueueSubscribe(subj, qgroup, cb)
|
|
ec.QueueSubscribe(subj, qgroup, cb)
|
|
|
|
ec.Flush()
|
|
|
|
// Helper function to send messages and check results.
|
|
sendAndCheckMsgs := func(numToSend int) {
|
|
for i := 0; i < numToSend; i++ {
|
|
ec.Publish(subj, i)
|
|
}
|
|
// Wait for processing.
|
|
ec.Flush()
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Check Results
|
|
checkResults(numToSend)
|
|
}
|
|
|
|
// Base Test
|
|
sendAndCheckMsgs(10)
|
|
|
|
// Stop and restart server
|
|
ts.Shutdown()
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
if err := Wait(reconnectsDone); err != nil {
|
|
t.Fatal("Did not get the ReconnectedCB!")
|
|
}
|
|
|
|
// Reconnect Base Test
|
|
sendAndCheckMsgs(10)
|
|
}
|
|
|
|
func TestIsClosed(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
nc := NewConnection(t, 22222)
|
|
defer nc.Close()
|
|
|
|
if nc.IsClosed() {
|
|
t.Fatalf("IsClosed returned true when the connection is still open.")
|
|
}
|
|
ts.Shutdown()
|
|
if nc.IsClosed() {
|
|
t.Fatalf("IsClosed returned true when the connection is still open.")
|
|
}
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
if nc.IsClosed() {
|
|
t.Fatalf("IsClosed returned true when the connection is still open.")
|
|
}
|
|
nc.Close()
|
|
if !nc.IsClosed() {
|
|
t.Fatalf("IsClosed returned false after Close() was called.")
|
|
}
|
|
}
|
|
|
|
func TestIsReconnectingAndStatus(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
disconnectedch := make(chan bool)
|
|
reconnectch := make(chan bool)
|
|
opts := nats.GetDefaultOptions()
|
|
opts.Url = "nats://localhost:22222"
|
|
opts.AllowReconnect = true
|
|
opts.MaxReconnect = 10000
|
|
opts.ReconnectWait = 100 * time.Millisecond
|
|
|
|
opts.DisconnectedCB = func(_ *nats.Conn) {
|
|
disconnectedch <- true
|
|
}
|
|
opts.ReconnectedCB = func(_ *nats.Conn) {
|
|
reconnectch <- true
|
|
}
|
|
|
|
// Connect, verify initial reconnecting state check, then stop the server
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
if nc.IsReconnecting() {
|
|
t.Fatalf("IsReconnecting returned true when the connection is still open.")
|
|
}
|
|
if status := nc.Status(); status != nats.CONNECTED {
|
|
t.Fatalf("Status returned %d when connected instead of CONNECTED", status)
|
|
}
|
|
ts.Shutdown()
|
|
|
|
// Wait until we get the disconnected callback
|
|
if e := Wait(disconnectedch); e != nil {
|
|
t.Fatalf("Disconnect callback wasn't triggered: %v", e)
|
|
}
|
|
if !nc.IsReconnecting() {
|
|
t.Fatalf("IsReconnecting returned false when the client is reconnecting.")
|
|
}
|
|
if status := nc.Status(); status != nats.RECONNECTING {
|
|
t.Fatalf("Status returned %d when reconnecting instead of CONNECTED", status)
|
|
}
|
|
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
// Wait until we get the reconnect callback
|
|
if e := Wait(reconnectch); e != nil {
|
|
t.Fatalf("Reconnect callback wasn't triggered: %v", e)
|
|
}
|
|
if nc.IsReconnecting() {
|
|
t.Fatalf("IsReconnecting returned true after the connection was reconnected.")
|
|
}
|
|
if status := nc.Status(); status != nats.CONNECTED {
|
|
t.Fatalf("Status returned %d when reconnected instead of CONNECTED", status)
|
|
}
|
|
|
|
// Close the connection, reconnecting should still be false
|
|
nc.Close()
|
|
if nc.IsReconnecting() {
|
|
t.Fatalf("IsReconnecting returned true after Close() was called.")
|
|
}
|
|
if status := nc.Status(); status != nats.CLOSED {
|
|
t.Fatalf("Status returned %d after Close() was called instead of CLOSED", status)
|
|
}
|
|
}
|
|
|
|
func TestFullFlushChanDuringReconnect(t *testing.T) {
|
|
ts := startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
reconnectch := make(chan bool)
|
|
|
|
opts := nats.GetDefaultOptions()
|
|
opts.Url = "nats://localhost:22222"
|
|
opts.AllowReconnect = true
|
|
opts.MaxReconnect = 10000
|
|
opts.ReconnectWait = 100 * time.Millisecond
|
|
|
|
opts.ReconnectedCB = func(_ *nats.Conn) {
|
|
reconnectch <- true
|
|
}
|
|
|
|
// Connect
|
|
nc, err := opts.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
// Channel used to make the go routine sending messages to stop.
|
|
stop := make(chan bool)
|
|
|
|
// While connected, publish as fast as we can
|
|
go func() {
|
|
for i := 0; ; i++ {
|
|
_ = nc.Publish("foo", []byte("hello"))
|
|
|
|
// Make sure we are sending at least flushChanSize (1024) messages
|
|
// before potentially pausing.
|
|
if i%2000 == 0 {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Send a bit...
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Shut down the server
|
|
ts.Shutdown()
|
|
|
|
// Continue sending while we are disconnected
|
|
time.Sleep(time.Second)
|
|
|
|
// Restart the server
|
|
ts = startReconnectServer(t)
|
|
defer ts.Shutdown()
|
|
|
|
// Wait for the reconnect CB to be invoked (but not for too long)
|
|
if e := WaitTime(reconnectch, 5*time.Second); e != nil {
|
|
t.Fatalf("Reconnect callback wasn't triggered: %v", e)
|
|
}
|
|
}
|
|
|
|
func TestReconnectVerbose(t *testing.T) {
|
|
s := RunDefaultServer()
|
|
defer s.Shutdown()
|
|
|
|
o := nats.GetDefaultOptions()
|
|
o.Verbose = true
|
|
rch := make(chan bool)
|
|
o.ReconnectedCB = func(_ *nats.Conn) {
|
|
rch <- true
|
|
}
|
|
|
|
nc, err := o.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
err = nc.Flush()
|
|
if err != nil {
|
|
t.Fatalf("Error during flush: %v", err)
|
|
}
|
|
|
|
s.Shutdown()
|
|
s = RunDefaultServer()
|
|
defer s.Shutdown()
|
|
|
|
if e := Wait(rch); e != nil {
|
|
t.Fatal("Should have reconnected ok")
|
|
}
|
|
|
|
err = nc.Flush()
|
|
if err != nil {
|
|
t.Fatalf("Error during flush: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReconnectBufSizeOption(t *testing.T) {
|
|
s := RunDefaultServer()
|
|
defer s.Shutdown()
|
|
|
|
nc, err := nats.Connect("nats://localhost:4222", nats.ReconnectBufSize(32))
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
if nc.Opts.ReconnectBufSize != 32 {
|
|
t.Fatalf("ReconnectBufSize should be 32 but it is %d", nc.Opts.ReconnectBufSize)
|
|
}
|
|
}
|
|
|
|
func TestReconnectBufSize(t *testing.T) {
|
|
s := RunDefaultServer()
|
|
defer s.Shutdown()
|
|
|
|
o := nats.GetDefaultOptions()
|
|
o.ReconnectBufSize = 32 // 32 bytes
|
|
|
|
dch := make(chan bool)
|
|
o.DisconnectedCB = func(_ *nats.Conn) {
|
|
dch <- true
|
|
}
|
|
|
|
nc, err := o.Connect()
|
|
if err != nil {
|
|
t.Fatalf("Should have connected ok: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
err = nc.Flush()
|
|
if err != nil {
|
|
t.Fatalf("Error during flush: %v", err)
|
|
}
|
|
|
|
// Force disconnected state.
|
|
s.Shutdown()
|
|
|
|
if e := Wait(dch); e != nil {
|
|
t.Fatal("DisconnectedCB should have been triggered")
|
|
}
|
|
|
|
msg := []byte("food") // 4 bytes paylaod, total proto is 16 bytes
|
|
// These should work, 2X16 = 32
|
|
if err := nc.Publish("foo", msg); err != nil {
|
|
t.Fatalf("Failed to publish message: %v\n", err)
|
|
}
|
|
if err := nc.Publish("foo", msg); err != nil {
|
|
t.Fatalf("Failed to publish message: %v\n", err)
|
|
}
|
|
|
|
// This should fail since we have exhausted the backing buffer.
|
|
if err := nc.Publish("foo", msg); err == nil {
|
|
t.Fatalf("Expected to fail to publish message: got no error\n")
|
|
}
|
|
nc.Buffered()
|
|
}
|