Feat(meeting): minimise sequential user pick (#13831)

Updating random user picker to reduce the probability that the exact same viewers would be selected first in successive random-pick runs
This commit is contained in:
hiroshisuga 2021-12-08 02:36:34 +09:00 committed by GitHub
parent 1a89f66c0e
commit 0bc62f66f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 13 deletions

View File

@ -50,6 +50,8 @@ trait SystemConfiguration {
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
lazy val reduceDuplicatedPick = Try(config.getBoolean("apps.reduceDuplicatedPick")).getOrElse(false)
// Redis server configuration
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)

View File

@ -5,6 +5,7 @@ import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.SystemConfiguration
import scala.util.Random
trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
@ -15,7 +16,7 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: Integer): Unit = {
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -31,11 +32,27 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
val reason = "No permission to select random user."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
val users = Users2x.findNotPresentersNorModerators(liveMeeting.users2x)
val users = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, false)
val usersPicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
val randNum = new scala.util.Random
val pickedUser = if (usersPicked.size == 0) "" else usersPicked(randNum.nextInt(usersPicked.size)).intId
if (reduceDuplicatedPick) {
if (usersPicked.size == 1) {
// Initialise the exemption
val usersToUnexempt = Users2x.findAll(liveMeeting.users2x)
usersToUnexempt foreach { u =>
Users2x.setUserExempted(liveMeeting.users2x, u.intId, false)
}
} else if (usersPicked.size > 1) {
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
}
val userIds = users.map { case (v) => v.intId }
broadcastEvent(msg, userIds, if (users.size == 0) -1 else randNum.nextInt(users.size))
broadcastEvent(msg, userIds, pickedUser)
}
}
}

View File

@ -71,8 +71,13 @@ object Users2x {
users.toVector.filter(u => !u.presenter)
}
def findNotPresentersNorModerators(users: Users2x): Vector[UserState] = {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE)
def getRandomlyPickableUsers(users: Users2x, reduceDup: Boolean): Vector[UserState] = {
if (reduceDup) {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left && !u.pickExempted)
} else {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left)
}
}
def findViewers(users: Users2x): Vector[UserState] = {
@ -150,6 +155,16 @@ object Users2x {
}
}
def setUserExempted(users: Users2x, intId: String, exempted: Boolean): Option[UserState] = {
for {
u <- findWithIntId(users, intId)
} yield {
val newUser = u.modify(_.pickExempted).setTo(exempted)
users.save(newUser)
newUser
}
}
def hasPresenter(users: Users2x): Boolean = {
findPresenter(users) match {
case Some(p) => true
@ -294,6 +309,7 @@ case class UserState(
lastActivityTime: Long = System.currentTimeMillis(),
lastInactivityInspect: Long = 0,
clientType: String,
pickExempted: Boolean,
userLeftFlag: UserLeftFlag
)

View File

@ -64,6 +64,7 @@ trait HandlerHelpers extends SystemConfiguration {
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = regUser.avatarURL,
clientType = clientType,
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0)
)
}

View File

@ -69,7 +69,7 @@ trait FakeTestData {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown",
userLeftFlag = UserLeftFlag(false, 0))
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
}
}

View File

@ -76,6 +76,7 @@ apps {
ejectOnViolation = false
endMeetingWhenNoMoreAuthedUsers = false
endMeetingWhenNoMoreAuthedUsersAfterMinutes = 2
reduceDuplicatedPick = false
}
analytics {

View File

@ -409,4 +409,4 @@ case class SelectRandomViewerReqMsgBody(requestedBy: String)
*/
object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: Integer)
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: String)

View File

@ -8,7 +8,7 @@ export default function randomlySelectedUser({ header, body }) {
check(meetingId, String);
check(requestedBy, String);
check(userIds, Array);
check(choice, Number);
check(choice, String);
updateRandomViewer(meetingId, userIds, choice, requestedBy);
}

View File

@ -5,7 +5,7 @@ import { check } from 'meteor/check';
export default function updateRandomUser(meetingId, userIds, choice, requesterId) {
check(meetingId, String);
check(userIds, Array);
check(choice, Number);
check(choice, String);
check(requesterId, String);
const selector = {
@ -13,7 +13,7 @@ export default function updateRandomUser(meetingId, userIds, choice, requesterId
};
const userList = [];
if (choice < 0) { // no viewer
if (choice == "") { // no viewer
userList.push([requesterId,0]);
} else if (userIds.length == 1) {
userList.push([userIds[0],0]);
@ -26,7 +26,7 @@ export default function updateRandomUser(meetingId, userIds, choice, requesterId
}
userList.push([userId, intervals.shift()]);
}
userList[userList.length-1][0] = userIds[choice]; // last one should be chosen in akka-app
userList[userList.length-1][0] = choice; // last one should be chosen in akka-app
}
if (userIds.length == 2) {

View File

@ -126,7 +126,8 @@ class RandomUserSelect extends Component {
const counter = SELECT_RANDOM_USER_COUNTDOWN ? this.state.count : 0;
if (mappedRandomlySelectedUsers.length < counter + 1) return null;
const selectedUser = mappedRandomlySelectedUsers[counter][0];
const selectedUser = SELECT_RANDOM_USER_COUNTDOWN ? mappedRandomlySelectedUsers[counter][0] :
mappedRandomlySelectedUsers[mappedRandomlySelectedUsers.length - 1][0];
const countDown = SELECT_RANDOM_USER_COUNTDOWN ?
mappedRandomlySelectedUsers.length - this.state.count - 1 : 0;