Skip to content

Deadlock when opening and closing connections concurrently #38

@joao-r-reis

Description

@joao-r-reis

I ran into two different issues, one is a deadlock and the other is leaked goroutines. I'm not exactly sure if these goroutines will be cleaned at some point but I did see them in the debugger.

Deadlock

While trying to reproduce #36 with an automated test, I ran into a deadlock caused by a blocking channel write here that causes the goroutine to not release the connectionsLock.

In the debugger I see a lot of goroutines blocked while trying to acquire that lock which is not possible because of that blocked channel write in the first goroutine.

I believe that this is because that channel is in fact nil due to this call to close() and writing to a nil channel blocks forever.

Leaked goroutines

I also see several background goroutines blocked here and here.

This happens because neither channel is buffered so the write will only progress when a read happens but the for loop returns before reading from both when an error happens.


Here is the test that I used to reproduce these issues:

func TestDeadlock(t *testing.T) {
	server := client.NewCqlServer(
		"127.0.0.1:9043",
		&client.AuthCredentials{
			Username: "cassandra",
			Password: "cassandra",
		},
	)
	defer server.Close()

	clt := client.NewCqlClient(
		"127.0.0.1:9043",
		&client.AuthCredentials{
			Username: "cassandra",
			Password: "cassandra",
		},
	)

	numberOfGoroutines := 10
	errChan := make(chan error, numberOfGoroutines)
	wg := &sync.WaitGroup{}
	defer wg.Wait()
	ctx, cancelFn := context.WithTimeout(context.Background(), 5 * time.Second)
	defer cancelFn()

	err := server.Start(ctx)
	require.Nil(t, err)

	for i := 0; i < numberOfGoroutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for ctx.Err() == nil {
				newCtx, newCancelFn := context.WithTimeout(ctx, time.Duration(rand.Intn(50)) * time.Millisecond)
				clientConn, serverConn, err := server.BindAndInit(
					clt, newCtx, primitive.ProtocolVersion4, client.ManagedStreamId)
				if err == nil {
					err = clientConn.InitiateHandshake(primitive.ProtocolVersion4, client.ManagedStreamId)
				}
				newCancelFn()
				if clientConn != nil {
					clientConn.Close()
				}
				if serverConn != nil {
					serverConn.Close()
				}
				if err != nil && newCtx.Err() == nil {
					errChan <- err
					return
				}
			}
		}()
	}

	select {
	case err = <-errChan:
		t.Errorf("%v", err)
	case <-ctx.Done():
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions