@ -101,6 +101,7 @@ class GroupCoordinator(val brokerId: Int,
@@ -101,6 +101,7 @@ class GroupCoordinator(val brokerId: Int,
def handleJoinGroup ( groupId : String ,
memberId : String ,
requireKnownMemberId : Boolean ,
clientId : String ,
clientHost : String ,
rebalanceTimeoutMs : Int ,
@ -117,24 +118,63 @@ class GroupCoordinator(val brokerId: Int,
@@ -117,24 +118,63 @@ class GroupCoordinator(val brokerId: Int,
sessionTimeoutMs > groupConfig . groupMaxSessionTimeoutMs ) {
responseCallback ( joinError ( memberId , Errors . INVALID_SESSION_TIMEOUT ) )
} else {
// only try to create the group if the group is not unknown AND
// the member id is UNKNOWN , if member is specified but group does not
// exist we should reject the request
groupManager . getGroup ( groupId ) match {
case None =>
if ( memberId != JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
} else {
// only try to create the group if the group is UNKNOWN AND
// the member id is UNKNOWN , if member is specified but group does not
// exist we should reject the request .
if ( memberId == JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
val group = groupManager . addGroup ( new GroupMetadata ( groupId , Empty , time ) )
doJoinGroup ( group , memberId , clientId , clientHost , rebalanceTimeoutMs , sessionTimeoutMs , protocolType , protocols , responseCallback )
doUnknownJoinGroup ( group , requireKnownMemberId , clientId , clientHost , rebalanceTimeoutMs , sessionTimeoutMs , protocolType , protocols , responseCallback )
} else {
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
}
case Some ( group ) =>
doJoinGroup ( group , memberId , clientId , clientHost , rebalanceTimeoutMs , sessionTimeoutMs , protocolType , protocols , responseCallback )
if ( memberId == JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
doUnknownJoinGroup ( group , requireKnownMemberId , clientId , clientHost , rebalanceTimeoutMs , sessionTimeoutMs , protocolType , protocols , responseCallback )
} else {
doJoinGroup ( group , memberId , clientId , clientHost , rebalanceTimeoutMs , sessionTimeoutMs , protocolType , protocols , responseCallback )
}
}
}
}
private def doUnknownJoinGroup ( group : GroupMetadata ,
requireKnownMemberId : Boolean ,
clientId : String ,
clientHost : String ,
rebalanceTimeoutMs : Int ,
sessionTimeoutMs : Int ,
protocolType : String ,
protocols : List [ ( String , Array [ Byte ] ) ] ,
responseCallback : JoinCallback ) : Unit = {
group . inLock {
if ( group . is ( Dead ) ) {
// if the group is marked as dead , it means some other thread has just removed the group
// from the coordinator metadata ; it is likely that the group has migrated to some other
// coordinator OR the group is in a transient unstable phase . Let the member retry
// joining without the specified member id .
responseCallback ( joinError ( JoinGroupRequest . UNKNOWN_MEMBER_ID , Errors . UNKNOWN_MEMBER_ID ) )
} else if ( ! group . supportsProtocols ( protocolType , MemberMetadata . plainProtocolSet ( protocols ) ) ) {
responseCallback ( joinError ( JoinGroupRequest . UNKNOWN_MEMBER_ID , Errors . INCONSISTENT_GROUP_PROTOCOL ) )
} else {
val newMemberId = clientId + "-" + group . generateMemberIdSuffix
if ( requireKnownMemberId ) {
// If member id required , register the member in the pending member list
// and send back a response to call for another join group request with allocated member id .
group . addPendingMember ( newMemberId )
addPendingMemberExpiration ( group , newMemberId , sessionTimeoutMs )
responseCallback ( joinError ( newMemberId , Errors . MEMBER_ID_REQUIRED ) )
} else {
addMemberAndRebalance ( rebalanceTimeoutMs , sessionTimeoutMs , newMemberId , clientId , clientHost , protocolType ,
protocols , group , responseCallback )
}
}
}
}
private def doJoinGroup ( group : GroupMetadata ,
memberId : String ,
clientId : String ,
@ -145,84 +185,74 @@ class GroupCoordinator(val brokerId: Int,
@@ -145,84 +185,74 @@ class GroupCoordinator(val brokerId: Int,
protocols : List [ ( String , Array [ Byte ] ) ] ,
responseCallback : JoinCallback ) {
group . inLock {
if ( ! group . is ( Empty ) && ( ! group . protocolType . contains ( protocolType ) || ! group . supportsProtocols ( protocols . map ( _ . _1 ) . toSet ) ) ) {
// if the new member does not support the group protocol , reject it
responseCallback ( joinError ( memberId , Errors . INCONSISTENT_GROUP_PROTOCOL ) )
} else if ( group . is ( Empty ) && ( protocols . isEmpty || protocolType . isEmpty ) ) {
// reject if first member with empty group protocol or protocolType is empty
if ( group . is ( Dead ) ) {
// if the group is marked as dead , it means some other thread has just removed the group
// from the coordinator metadata ; this is likely that the group has migrated to some other
// coordinator OR the group is in a transient unstable phase . Let the member retry
// joining without the specified member id .
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
} else if ( ! group . supportsProtocols ( protocolType , MemberMetadata . plainProtocolSet ( protocols ) ) ) {
responseCallback ( joinError ( memberId , Errors . INCONSISTENT_GROUP_PROTOCOL ) )
} else if ( memberId != JoinGroupRequest . UNKNOWN_MEMBER_ID && ! group . has ( memberId ) ) {
} else if ( group . isPendingMember ( memberId ) ) {
// A rejoining pending member will be accepted .
addMemberAndRebalance ( rebalanceTimeoutMs , sessionTimeoutMs , memberId , clientId , clientHost , protocolType ,
protocols , group , responseCallback )
} else if ( ! group . has ( memberId ) ) {
// if the member trying to register with a un - recognized id , send the response to let
// it reset its member id and retry
// it reset its member id and retry .
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
} else {
group . currentState match {
case Dead =>
// if the group is marked as dead , it means some other thread has just removed the group
// from the coordinator metadata ; this is likely that the group has migrated to some other
// coordinator OR the group is in a transient unstable phase . Let the member retry
// joining without the specified member id ,
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
case PreparingRebalance =>
if ( memberId == JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
addMemberAndRebalance ( rebalanceTimeoutMs , sessionTimeoutMs , clientId , clientHost , protocolType ,
protocols , group , responseCallback )
} else {
val member = group . get ( memberId )
updateMemberAndRebalance ( group , member , protocols , responseCallback )
}
val member = group . get ( memberId )
updateMemberAndRebalance ( group , member , protocols , responseCallback )
case CompletingRebalance =>
if ( memberId == JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
addMemberAndRebalance ( rebalanceTimeoutMs , sessionTimeoutMs , clientId , clientHost , protocolType ,
protocols , group , responseCallback )
val member = group . get ( memberId )
if ( member . matches ( protocols ) ) {
// member is joining with the same metadata ( which could be because it failed to
// receive the initial JoinGroup response ) , so just return current group information
// for the current generation .
responseCallback ( JoinGroupResult (
members = if ( group . isLeader ( memberId ) ) {
group . currentMemberMetadata
} else {
Map . empty
} ,
memberId = memberId ,
generationId = group . generationId ,
subProtocol = group . protocolOrNull ,
leaderId = group . leaderOrNull ,
error = Errors . NONE ) )
} else {
val member = group . get ( memberId )
if ( member . matches ( protocols ) ) {
// member is joining with the same metadata ( which could be because it failed to
// receive the initial JoinGroup response ) , so just return current group information
// for the current generation .
responseCallback ( JoinGroupResult (
members = if ( group . isLeader ( memberId ) ) {
group . currentMemberMetadata
} else {
Map . empty
} ,
memberId = memberId ,
generationId = group . generationId ,
subProtocol = group . protocolOrNull ,
leaderId = group . leaderOrNull ,
error = Errors . NONE ) )
} else {
// member has changed metadata , so force a rebalance
updateMemberAndRebalance ( group , member , protocols , responseCallback )
}
// member has changed metadata , so force a rebalance
updateMemberAndRebalance ( group , member , protocols , responseCallback )
}
case Empty | Stable =>
if ( memberId == JoinGroupRequest . UNKNOWN_MEMBER_ID ) {
// if the member id is unknown , register the member to the group
addMemberAndRebalance ( rebalanceTimeoutMs , sessionTimeoutMs , clientId , clientHost , protocolType ,
protocols , group , responseCallback )
case Stable =>
val member = group . get ( memberId )
if ( group . isLeader ( memberId ) || ! member . matches ( protocols ) ) {
// force a rebalance if a member has changed metadata or if the leader sends JoinGroup .
// The latter allows the leader to trigger rebalances for changes affecting assignment
// which do not affect the member metadata ( such as topic metadata changes for the consumer )
updateMemberAndRebalance ( group , member , protocols , responseCallback )
} else {
val member = group . get ( memberId )
if ( group . isLeader ( memberId ) || ! member . matches ( protocols ) ) {
// force a rebalance if a member has changed metadata or if the leader sends JoinGroup .
// The latter allows the leader to trigger rebalances for changes affecting assignment
// which do not affect the member metadata ( such as topic metadata changes for the consumer )
updateMemberAndRebalance ( group , member , protocols , responseCallback )
} else {
// for followers with no actual change to their metadata , just return group information
// for the current generation which will allow them to issue SyncGroup
responseCallback ( JoinGroupResult (
members = Map . empty ,
memberId = memberId ,
generationId = group . generationId ,
subProtocol = group . protocolOrNull ,
leaderId = group . leaderOrNull ,
error = Errors . NONE ) )
}
// for followers with no actual change to their metadata , just return group information
// for the current generation which will allow them to issue SyncGroup
responseCallback ( JoinGroupResult (
members = Map . empty ,
memberId = memberId ,
generationId = group . generationId ,
subProtocol = group . protocolOrNull ,
leaderId = group . leaderOrNull ,
error = Errors . NONE ) )
}
case Empty | Dead =>
// Group reaches unexpected state . Let the joining member reset their generation and rejoin .
warn ( s" Attempt to add rejoining member ${ memberId } of group ${ group . groupId } in " +
s" unexpected group state ${ group . currentState } " )
responseCallback ( joinError ( memberId , Errors . UNKNOWN_MEMBER_ID ) )
}
if ( group . is ( PreparingRebalance ) )
@ -320,7 +350,7 @@ class GroupCoordinator(val brokerId: Int,
@@ -320,7 +350,7 @@ class GroupCoordinator(val brokerId: Int,
groupManager . getGroup ( groupId ) match {
case None =>
// if the group is marked as dead , it means some other thread has just removed the group
// from the coordinator metadata ; this is likely that the group has migrated to some other
// from the coordinator metadata ; it is likely that the group has migrated to some other
// coordinator OR the group is in a transient unstable phase . Let the consumer to retry
// joining without specified consumer id ,
responseCallback ( Errors . UNKNOWN_MEMBER_ID )
@ -402,9 +432,9 @@ class GroupCoordinator(val brokerId: Int,
@@ -402,9 +432,9 @@ class GroupCoordinator(val brokerId: Int,
group . currentState match {
case Dead =>
// if the group is marked as dead , it means some other thread has just removed the group
// from the coordinator metadata ; this is likely that the group has migrated to some other
// from the coordinator metadata ; it is likely that the group has migrated to some other
// coordinator OR the group is in a transient unstable phase . Let the member retry
// joining without the specified member id ,
// joining without the specified member id .
responseCallback ( Errors . UNKNOWN_MEMBER_ID )
case Empty =>
@ -664,7 +694,7 @@ class GroupCoordinator(val brokerId: Int,
@@ -664,7 +694,7 @@ class GroupCoordinator(val brokerId: Int,
JoinGroupResult (
members = Map . empty ,
memberId = memberId ,
generationId = 0 ,
generationId = GroupCoordinator . NoGeneration ,
subProtocol = GroupCoordinator . NoProtocol ,
leaderId = GroupCoordinator . NoLeader ,
error = error )
@ -677,7 +707,7 @@ class GroupCoordinator(val brokerId: Int,
@@ -677,7 +707,7 @@ class GroupCoordinator(val brokerId: Int,
completeAndScheduleNextExpiration ( group , member , member . sessionTimeoutMs )
}
private def completeAndScheduleNextExpiration ( group : GroupMetadata , member : MemberMetadata , timeoutMs : Long ) : Unit = {
private def completeAndScheduleNextExpiration ( group : GroupMetadata , member : MemberMetadata , timeoutMs : Long ) {
// complete current heartbeat expectation
member . latestHeartbeat = time . milliseconds ( )
val memberKey = MemberKey ( member . groupId , member . memberId )
@ -685,10 +715,20 @@ class GroupCoordinator(val brokerId: Int,
@@ -685,10 +715,20 @@ class GroupCoordinator(val brokerId: Int,
// reschedule the next heartbeat expiration deadline
val deadline = member . latestHeartbeat + timeoutMs
val delayedHeartbeat = new DelayedHeartbeat ( this , group , member , deadline , timeoutMs )
val delayedHeartbeat = new DelayedHeartbeat ( this , group , member . memberId , isPending = false , deadline , timeoutMs )
heartbeatPurgatory . tryCompleteElseWatch ( delayedHeartbeat , Seq ( memberKey ) )
}
/* *
* Add pending member expiration to heartbeat purgatory
*/
private def addPendingMemberExpiration ( group : GroupMetadata , pendingMemberId : String , timeoutMs : Long ) {
val pendingMemberKey = MemberKey ( group . groupId , pendingMemberId )
val deadline = time . milliseconds ( ) + timeoutMs
val delayedHeartbeat = new DelayedHeartbeat ( this , group , pendingMemberId , isPending = true , deadline , timeoutMs )
heartbeatPurgatory . tryCompleteElseWatch ( delayedHeartbeat , Seq ( pendingMemberKey ) )
}
private def removeHeartbeatForLeavingMember ( group : GroupMetadata , member : MemberMetadata ) {
member . isLeaving = true
val memberKey = MemberKey ( member . groupId , member . memberId )
@ -697,13 +737,13 @@ class GroupCoordinator(val brokerId: Int,
@@ -697,13 +737,13 @@ class GroupCoordinator(val brokerId: Int,
private def addMemberAndRebalance ( rebalanceTimeoutMs : Int ,
sessionTimeoutMs : Int ,
memberId : String ,
clientId : String ,
clientHost : String ,
protocolType : String ,
protocols : List [ ( String , Array [ Byte ] ) ] ,
group : GroupMetadata ,
callback : JoinCallback ) : MemberMetadata = {
val memberId = clientId + "-" + group . generateMemberIdSuffix
val member = new MemberMetadata ( memberId , group . groupId , clientId , clientHost , rebalanceTimeoutMs ,
sessionTimeoutMs , protocolType , protocols )
@ -724,6 +764,7 @@ class GroupCoordinator(val brokerId: Int,
@@ -724,6 +764,7 @@ class GroupCoordinator(val brokerId: Int,
completeAndScheduleNextExpiration ( group , member , NewMemberJoinTimeoutMs )
maybePrepareRebalance ( group , s" Adding new member $memberId " )
group . removePendingMember ( memberId )
member
}
@ -844,19 +885,32 @@ class GroupCoordinator(val brokerId: Int,
@@ -844,19 +885,32 @@ class GroupCoordinator(val brokerId: Int,
}
}
def tryCompleteHeartbeat ( group : GroupMetadata , member : MemberMetadata , heartbeatDeadline : Long , forceComplete : ( ) => Boolean ) = {
def tryCompleteHeartbeat ( group : GroupMetadata , memberId : String , isPending : Boolean , heartbeatDeadline : Long , forceComplete : ( ) => Boolean ) = {
group . inLock {
if ( member . shouldKeepAlive ( heartbeatDeadline ) || member . isLeaving )
forceComplete ( )
else false
if ( isPending )
group . has ( memberId )
else {
val member = group . get ( memberId )
if ( member . shouldKeepAlive ( heartbeatDeadline ) || member . isLeaving ) {
forceComplete ( )
} else false
}
}
}
def onExpireHeartbeat ( group : GroupMetadata , member : MemberMetadata , heartbeatDeadline : Long ) {
def onExpireHeartbeat ( group : GroupMetadata , memberId : String , isPending : Boolean , heartbeatDeadline : Long ) {
group . inLock {
if ( ! member . shouldKeepAlive ( heartbeatDeadline ) ) {
info ( s" Member ${ member . memberId } in group ${ group . groupId } has failed, removing it from the group " )
removeMemberAndUpdateGroup ( group , member , s" removing member ${ member . memberId } on heartbeat expiration " )
if ( isPending ) {
debug ( s" Pending member $memberId has been removed after session timeout expiration. " )
group . removePendingMember ( memberId )
} else if ( ! group . has ( memberId ) ) {
debug ( s" Member $memberId has already been removed from the group. " )
} else {
val member = group . get ( memberId )
if ( ! member . shouldKeepAlive ( heartbeatDeadline ) ) {
info ( s" Member ${ member . memberId } in group ${ group . groupId } has failed, removing it from the group " )
removeMemberAndUpdateGroup ( group , member , s" removing member ${ member . memberId } on heartbeat expiration " )
}
}
}
}