hubcio commented on code in PR #3652:
URL: https://github.com/apache/iggy/pull/3652#discussion_r3568472401


##########
foreign/go/client/tcp/tcp_core.go:
##########
@@ -644,7 +652,8 @@ func (c *IggyTcpClient) shutdown() error {
                }
        }
 
-       c.state = iggcon.StateShutdown
+       c.transportState = iggcon.TransportStateShutdown
+       c.sessionState = iggcon.SessionStateUnauthenticated

Review Comment:
   pre-existing ordering, but this line extends it to `sessionState`: if 
`conn.Close()` errors, the early return above skips both state flips, leaving 
the client stuck reporting connected (and now authenticated too). 
`disconnect()` does it in the safer order - state first, then close. worth 
mirroring that here, or just not early-returning before the flips.



##########
foreign/go/client/tcp/tcp_session_management.go:
##########
@@ -30,41 +30,42 @@ import (
 )
 
 func (c *IggyTcpClient) LoginUser(ctx context.Context, username string, 
password string) (*iggcon.IdentityInfo, error) {
-       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
-       buffer, err := c.do(ctx, &command.LoginUser{
+       return c.login(ctx, &command.LoginUser{
                Username: username,
                Password: password,
        })
-       if err != nil {
-               return nil, err
-       }
-
-       c.logger.Info("Iggy client has signed in successfully.", 
slog.String("client_address", c.clientAddress))
-       identity := binaryserialization.DeserializeLogInResponse(buffer)
-       shouldRedirect, err := c.HandleLeaderRedirection(ctx)
-       if err != nil {
-               return nil, err
-       }
-       if shouldRedirect {
-               if err = c.Connect(ctx); err != nil {
-                       return nil, err
-               }
-               return c.LoginUser(ctx, username, password)
-       }
-       return identity, nil
 }
 
 func (c *IggyTcpClient) LoginWithPersonalAccessToken(ctx context.Context, 
token string) (*iggcon.IdentityInfo, error) {
-       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
-       buffer, err := c.do(ctx, &command.LoginWithPersonalAccessToken{
+       return c.login(ctx, &command.LoginWithPersonalAccessToken{
                Token: token,
        })
+}
+
+func (c *IggyTcpClient) login(ctx context.Context, loginCmd command.Command) 
(*iggcon.IdentityInfo, error) {
+       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
+       c.mtx.Lock()
+       pre := c.sessionState
+       c.sessionState = iggcon.SessionStateAuthenticating
+       c.mtx.Unlock()
+
+       buffer, err := c.do(ctx, loginCmd)
        if err != nil {
+               // A rejected login leaves the session state untouched, so 
restore the
+               // pre-login state only while it is still Authenticating: if 
the state
+               // moved, the connection may died mid-attempt and invalidation 
already
+               // recorded it unauthenticated, skip the restoration.
+               c.mtx.Lock()
+               if c.sessionState == iggcon.SessionStateAuthenticating {

Review Comment:
   there's a race here when two goroutines call `LoginUser` concurrently on the 
same client. the mutex is dropped between setting `Authenticating` (line 49) 
and this restore check, and `do()` takes its own lock, so two logins can 
interleave: A's connection dies mid-login (`invalidateConnLocked` sets 
`Unauthenticated`), B starts a login and sets `Authenticating` again, A 
re-locks, sees `Authenticating` and restores its stale `pre` = `Authenticated`. 
B then fails the disconnected gate and skips its own restore. end state: 
`sessionState = Authenticated` while `transportState = Disconnected`, both 
logins returned errors.
   
   nothing reads `sessionState` yet so this can't bite today, but #3650 will 
add exactly that reader (deciding whether to re-login on reconnect), so better 
to fix it before the machinery grows a consumer.
   
   simplest fix: drop the `Authenticating` intermediate and the `pre`/restore 
entirely - set `Authenticated` on success only, leave `sessionState` untouched 
on failure. connection death already resets it via `invalidateConnLocked`, and 
a server-side reject never touched it in the first place, so behavior is 
identical on all paths and `TestLoginUser_RejectedReloginKeepsExistingSession` 
still passes. ~10 lines less and no race. if #3650 needs an observable 
`Authenticating`, it can be re-added then together with real login 
serialization (an in-progress guard held across the whole attempt) - the 
current best-effort restore doesn't provide that anyway.



##########
foreign/go/contracts/state.go:
##########
@@ -17,30 +17,45 @@
 
 package iggcon
 
-type State uint8
+type TransportState uint8
 
 const (
-       StateShutdown State = iota
-       StateDisconnected
-       StateConnecting
-       StateConnected
-       StateAuthenticating
-       StateAuthenticated
+       TransportStateDisconnected TransportState = iota

Review Comment:
   removing the exported `State` type and consts is a compile-time break for 
anyone importing `iggcon` directly - deserves a breaking-change note and a 
minor (not patch) bump for the next tag. also the zero value changed meaning: 
`StateShutdown` was 0, now `TransportStateDisconnected` is 0, so a zero-value 
client is connectable instead of permanently shut down. arguably better, just 
worth a mention in the note.



##########
foreign/go/client/tcp/tcp_session_management.go:
##########
@@ -30,41 +30,42 @@ import (
 )
 
 func (c *IggyTcpClient) LoginUser(ctx context.Context, username string, 
password string) (*iggcon.IdentityInfo, error) {
-       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
-       buffer, err := c.do(ctx, &command.LoginUser{
+       return c.login(ctx, &command.LoginUser{
                Username: username,
                Password: password,
        })
-       if err != nil {
-               return nil, err
-       }
-
-       c.logger.Info("Iggy client has signed in successfully.", 
slog.String("client_address", c.clientAddress))
-       identity := binaryserialization.DeserializeLogInResponse(buffer)
-       shouldRedirect, err := c.HandleLeaderRedirection(ctx)
-       if err != nil {
-               return nil, err
-       }
-       if shouldRedirect {
-               if err = c.Connect(ctx); err != nil {
-                       return nil, err
-               }
-               return c.LoginUser(ctx, username, password)
-       }
-       return identity, nil
 }
 
 func (c *IggyTcpClient) LoginWithPersonalAccessToken(ctx context.Context, 
token string) (*iggcon.IdentityInfo, error) {
-       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
-       buffer, err := c.do(ctx, &command.LoginWithPersonalAccessToken{
+       return c.login(ctx, &command.LoginWithPersonalAccessToken{
                Token: token,
        })
+}
+
+func (c *IggyTcpClient) login(ctx context.Context, loginCmd command.Command) 
(*iggcon.IdentityInfo, error) {
+       c.logger.Info("Iggy client is signing in...", 
slog.String("client_address", c.clientAddress))
+       c.mtx.Lock()
+       pre := c.sessionState
+       c.sessionState = iggcon.SessionStateAuthenticating
+       c.mtx.Unlock()
+
+       buffer, err := c.do(ctx, loginCmd)
        if err != nil {
+               // A rejected login leaves the session state untouched, so 
restore the
+               // pre-login state only while it is still Authenticating: if 
the state
+               // moved, the connection may died mid-attempt and invalidation 
already

Review Comment:
   typo: "the connection may died" -> "may have died"



##########
foreign/go/client/tcp/tcp_core_test.go:
##########
@@ -272,3 +273,23 @@ func TestNewIggyTcpClient_StoresProvidedLogger(t 
*testing.T) {
                t.Errorf("expected logger output to contain 'source=tcp', got: 
%q", output)
        }
 }
+
+func TestLoginUser_RejectedReloginKeepsExistingSession(t *testing.T) {

Review Comment:
   two more asserts would pin the happy paths while you're here: login success 
sets `Authenticated`, and `LogoutUser` success resets to `Unauthenticated` - 
right now no test covers either transition.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to