The data race happens when listener.connect() is calling newDialListenerConn() and listener.Close() is checking if the connection is nil.
listener.connect() doesn't hold a mutex while establishing the connection and hasn't set the listener.cn field yet, so Close() grabs the mutex, sees that the field is nil, and sets l.isClosed to true. Once connect() establishes the connection, it's not aware that the listener has been closed and sets the listener.cn field normally, which is then never cleaned up.