Пример #1
0
// TestPool tests the pool is functional.
func TestPool(t *testing.T) {
	tests.ResetLog()
	defer tests.DisplayLog()

	t.Log("Given the need to validate the work pool functions.")
	{
		cfg := pool.Config{
			MinRoutines: func() int { return 100 },
			MaxRoutines: func() int { return 5000 },
		}

		p, err := pool.New("TestPool", "Pool1", cfg)
		if err != nil {
			t.Fatal("\tShould not get error creating pool.", tests.Failed, err)
		}
		t.Log("\tShould not get error creating pool.", tests.Success)

		for i := 0; i < 100; i++ {
			p.Do("TestPool", &theWork{privateID: i})
		}

		time.Sleep(100 * time.Millisecond)

		p.Shutdown("TestPool")
	}
}
Пример #2
0
// ExampleNew provides a basic example for using a pool.
func ExampleNew() {
	tests.ResetLog()
	defer tests.DisplayLog()

	// Create a configuration.
	cfg := pool.Config{
		MinRoutines: func() int { return 3 },
		MaxRoutines: func() int { return 4 },
	}

	// Create a new pool.
	p, err := pool.New("TEST", "TheWork", cfg)
	if err != nil {
		fmt.Println(err)
		return
	}

	// Pass in some work to be performed.
	p.Do("TEST", &theWork{})
	p.Do("TEST", &theWork{})
	p.Do("TEST", &theWork{})

	// Wait to the work to be processed.
	time.Sleep(100 * time.Millisecond)

	// Shutdown the pool.
	p.Shutdown("TEST")
}
Пример #3
0
func main() {
	const context = "main"
	const totalWork = 100

	wg.Add(totalWork)

	// Create the configuration.
	cfg := pool.Config{
		MinRoutines: func() int { return cfg.MustInt(cfgMinRoutines) },
		MaxRoutines: func() int { return cfg.MustInt(cfgMaxRoutines) },
	}

	// Create a pool.
	p, err := pool.New(context, "test", cfg)
	if err != nil {
		log.Error(context, "main", err, "Creating pool")
		return
	}

	// Look at stats for the work.
	go func() {
		for {
			time.Sleep(250 * time.Millisecond)
			log.User(context, "Stats", "%#v", p.Stats())
		}
	}()

	// Perform some work.
	for i := 0; i < totalWork; i++ {
		p.Do(context, &Task{Name: strconv.Itoa(i)})
	}

	// Wait until all the work is complete.
	wg.Wait()

	// Shutdown the pool.
	p.Shutdown(context)
}
Пример #4
0
// TestRateLimit tests we can drop connections when they come in too fast.
func TestRateLimit(t *testing.T) {
	tests.ResetLog()
	defer tests.DisplayLog()

	const ratelimit = 1 * time.Second

	t.Log("Given the need to drop TCP connections.")
	{
		recvCfg := pool.Config{
			MinRoutines: func() int { return 2 },
			MaxRoutines: func() int { return 1000 },
		}

		recv, err := pool.New(tests.Context, "Test-Recv", recvCfg)
		if err != nil {
			t.Fatal("\tShould be able to create a work pool for the recv.", tests.Failed, err)
		}

		sendCfg := pool.Config{
			MinRoutines: func() int { return 2 },
			MaxRoutines: func() int { return 1000 },
		}

		send, err := pool.New(tests.Context, "Test-Send", sendCfg)
		if err != nil {
			t.Fatal("\tShould be able to create a work pool for the send.", tests.Failed, err)
		}

		// Create a configuration.
		cfg := tcp.Config{
			NetType:     "tcp4",
			Addr:        ":0",
			ConnHandler: tcpConnHandler{},
			ReqHandler:  tcpReqHandler{},
			RespHandler: tcpRespHandler{},

			OptUserPool: tcp.OptUserPool{
				RecvPool: recv,
				SendPool: send,
			},

			OptRateLimit: tcp.OptRateLimit{
				RateLimit: func() time.Duration { return ratelimit },
			},
		}

		// Create a new TCP value.
		u, err := tcp.New(tests.Context, "TEST", cfg)
		if err != nil {
			t.Fatal("\tShould be able to create a new TCP listener.", tests.Failed, err)
		}
		t.Log("\tShould be able to create a new TCP listener.", tests.Success)

		// Start accepting client data.
		if err := u.Start(tests.Context); err != nil {
			t.Fatal("\tShould be able to start the TCP listener.", tests.Failed, err)
		}
		t.Log("\tShould be able to start the TCP listener.", tests.Success)

		defer u.Stop(tests.Context)

		newconn := func() (*bufio.Writer, *bufio.Reader, net.Conn, error) {
			// Let's connect to the host:port.
			conn, err := net.Dial("tcp4", u.Addr().String())
			if err != nil {
				return nil, nil, nil, err
			}
			return bufio.NewWriter(conn), bufio.NewReader(conn), conn, nil
		}

		// Make a successful connection
		successfulTest := func(ctx interface{}) {
			w, r, c, err := newconn()
			if err != nil {
				t.Fatal("\tShould be able to dial a new TCP connection.", ctx, tests.Failed, err)
			}
			t.Log("\tShould be able to dial a new TCP connection.", ctx, tests.Success)

			defer c.Close()

			if _, err := w.WriteString("Hello\n"); err != nil {
				t.Fatal("\tShould be able to send data to the connection.", ctx, tests.Failed, err)
			}
			t.Log("\tShould be able to send data to the connection.", ctx, tests.Success)

			if err := w.Flush(); err != nil {
				t.Fatal("\tShould be able to flush the writer.", ctx, tests.Failed, err)
			}
			t.Log("\tShould be able to flush the writer.", ctx, tests.Success)

			// Let's read the response.
			response, err := r.ReadString('\n')
			if err != nil {
				t.Fatal("\tShould be able to read the response from the connection.", ctx, tests.Failed, err)
			}
			t.Log("\tShould be able to read the response from the connection.", ctx, tests.Success)

			t.Log(response)
		}

		successfulTest("PRE-LIMIT")

		// The next 100 connections should fail (assuming it's all under rateLimit amount of time).
		for i := 0; i < 100; i++ {
			//  Apparently, even though the connection should not exist, we are still allowed
			//  to connect to the remote socket and write to it.  The error is exhibited
			//  only when it's time to perform a read on that connection.
			w, r, c, err := newconn()
			if err != nil {
				t.Fatal("\tShould be able to dial a non-first TCP connection.", tests.Failed, err)
			}
			t.Log("\tShould be able to dial a non-first TCP connection", c.LocalAddr(), tests.Success)

			defer c.Close()

			if _, err := w.WriteString("Hello\n"); err != nil {
				t.Fatal("\tShould be able to send data to the connection.", tests.Failed, err)
			}
			t.Log("\tShould be able to send data to the connection.", tests.Success)

			if err := w.Flush(); err != nil {
				t.Fatal("\tShould be able to flush the writer.", tests.Failed, err)
			}
			t.Log("\tShould be able to flush the writer.", tests.Success)

			// Let's read the response.
			_, err = r.ReadString('\n')
			if err == nil {
				t.Fatal("\tShould have tests.Failed to read from the connection.", tests.Failed)
			}
			t.Log("\tShould have tests.Failed to read from the connection", tests.Success, err)
		}

		// Sleep for rateLimit to perform another successful test.
		time.Sleep(ratelimit)
		successfulTest("POST-LIMIT")

		// NOTE If you call another 'successfulTest' here, we will fail because we expect
		// the test to fail due to the limit.
	}
}
Пример #5
0
// TestDropConnections tests we can drop connections when configured.
func TestDropConnections(t *testing.T) {
	tests.ResetLog()
	defer tests.DisplayLog()

	t.Log("Given the need to drop TCP connections.")
	{
		recvCfg := pool.Config{
			MinRoutines: func() int { return 2 },
			MaxRoutines: func() int { return 1000 },
		}

		recv, err := pool.New(tests.Context, "Test-Recv", recvCfg)
		if err != nil {
			t.Fatal("\tShould be able to create a work pool for the recv.", tests.Failed, err)
		}

		sendCfg := pool.Config{
			MinRoutines: func() int { return 2 },
			MaxRoutines: func() int { return 1000 },
		}

		send, err := pool.New(tests.Context, "Test-Send", sendCfg)
		if err != nil {
			t.Fatal("\tShould be able to create a work pool for the send.", tests.Failed, err)
		}

		// Create a configuration.
		cfg := tcp.Config{
			NetType:     "tcp4",
			Addr:        ":0",
			ConnHandler: tcpConnHandler{},
			ReqHandler:  tcpReqHandler{},
			RespHandler: tcpRespHandler{},

			OptUserPool: tcp.OptUserPool{
				RecvPool: recv,
				SendPool: send,
			},
		}

		// Create a new TCP value.
		u, err := tcp.New(tests.Context, "TEST", cfg)
		if err != nil {
			t.Fatal("\tShould be able to create a new TCP listener.", tests.Failed, err)
		}
		t.Log("\tShould be able to create a new TCP listener.", tests.Success)

		// Set the drop connection flag to true.
		t.Log("\tSet the drop connections flag to TRUE.", tests.Success)
		u.DropConnections(tests.Context, true)

		// Start accepting client data.
		if err := u.Start(tests.Context); err != nil {
			t.Fatal("\tShould be able to start the TCP listener.", tests.Failed, err)
		}
		t.Log("\tShould be able to start the TCP listener.", tests.Success)

		defer u.Stop(tests.Context)

		// Let's connect to the host:port.
		conn, err := net.Dial("tcp4", u.Addr().String())
		if err != nil {
			t.Fatal("\tShould be able to dial a new TCP connection.", tests.Failed, err)
		}
		t.Log("\tShould be able to dial a new TCP connection.", tests.Success)

		// An attempt to read should result in an EOF.
		b := make([]byte, 1)
		if _, err = conn.Read(b); err == nil {
			t.Fatal("\tShould not be able to read the response from the connection.", tests.Failed, err)
		}
		t.Log("\tShould not be able to read the response from the connection.", tests.Success)
	}
}
Пример #6
0
// New creates a new manager to service clients.
func New(context interface{}, name string, cfg Config) (*TCP, error) {
	log.Dev(context, "New", "Started : Name[%s] NetType[%s] Addr[%s] RecvMaxPoolSize[%d] SendMaxPoolSize[%d]", name, cfg.NetType, cfg.Addr, cfg.RecvMaxPoolSize, cfg.SendMaxPoolSize)

	// Validate the configuration.
	if err := cfg.Validate(); err != nil {
		log.Error(context, "New", err, "Completed")
		return nil, err
	}

	// Resolve the addr that is provided.
	tcpAddr, err := net.ResolveTCPAddr(cfg.NetType, cfg.Addr)
	if err != nil {
		log.Error(context, "New", err, "Completed")
		return nil, err
	}

	log.Dev(context, "New", "Address[ %s ] Zone[%s]", join(tcpAddr.IP.String(), tcpAddr.Port), tcpAddr.Zone)

	// Need a work pool to handle the received messages.
	var recv *pool.Pool
	if cfg.RecvPool != nil {
		recv = cfg.RecvPool
	} else {
		recvCfg := pool.Config{
			MinRoutines: cfg.RecvMinPoolSize,
			MaxRoutines: cfg.RecvMaxPoolSize,
		}

		var err error
		if recv, err = pool.New(context, name+"-Recv", recvCfg); err != nil {
			log.Error(context, "New", err, "Completed")
			return nil, err
		}
	}

	// Need a work pool to handle the messages to send.
	var send *pool.Pool
	if cfg.SendPool != nil {
		send = cfg.SendPool
	} else {
		sendCfg := pool.Config{
			MinRoutines: cfg.SendMinPoolSize,
			MaxRoutines: cfg.SendMaxPoolSize,
		}

		var err error
		if send, err = pool.New(context, name+"-Send", sendCfg); err != nil {
			log.Error(context, "New", err, "Completed")
			return nil, err
		}
	}

	// Are we using user provided work pools. Validation is helping us
	// only have to check one of the two configuration options for this.
	var userPools bool
	if cfg.RecvPool != nil {
		log.Dev(context, "New", "Using User Pools")
		userPools = true
	}

	// Create a TCP for this ipaddress and port.
	t := TCP{
		Config: cfg,
		Name:   name,

		ipAddress: tcpAddr.IP.String(),
		port:      tcpAddr.Port,
		tcpAddr:   tcpAddr,

		clients: make(map[string]*client),

		recv:      recv,
		send:      send,
		userPools: userPools,
	}

	log.Dev(context, "New", "Completed")
	return &t, nil
}