Merge pull request #16 from pedrobmarin/life-cycle-management

Moving the meeting life cycle management from bbb-web to akka-apps
This commit is contained in:
Richard Alam 2017-05-04 10:22:06 -04:00 committed by GitHub
commit a97dd6ec58
12 changed files with 85 additions and 267 deletions

View File

@ -47,6 +47,12 @@ inactivity {
timeLeft=300
}
expire {
# time in seconds
lastUserLeft = 60
neverJoined = 300
}
services {
bbbWebAPI = "http://192.168.23.33/bigbluebutton/api"
sharedSecret = "changeme"

View File

@ -24,4 +24,7 @@ trait SystemConfiguration {
lazy val inactivityDeadline = Try(config.getInt("inactivity.deadline")).getOrElse(2 * 3600) // 2 hours
lazy val inactivityTimeLeft = Try(config.getInt("inactivity.timeLeft")).getOrElse(5 * 60) // 5 minutes
lazy val expireLastUserLeft = Try(config.getInt("expire.lastUserLeft")).getOrElse(60) // 1 minute
lazy val expireNeverJoined = Try(config.getInt("expire.neverJoined")).getOrElse(5 * 60) // 5 minutes
}

View File

@ -40,6 +40,7 @@ case class DestroyMeeting(meetingID: String) extends InMessage
case class StartMeeting(meetingID: String) extends InMessage
case class EndMeeting(meetingId: String) extends InMessage
case class LockSetting(meetingID: String, locked: Boolean, settings: Map[String, Boolean]) extends InMessage
case class UpdateMeetingExpireMonitor(meetingID: String, hasUser: Boolean) extends InMessage
//////////////////////////////////////////////////////////////////////////////////////
// Breakout room

View File

@ -53,7 +53,7 @@ class MeetingActor(val mProps: MeetingProperties,
def receive = {
case msg: ActivityResponse => handleActivityResponse(msg)
case msg: MonitorNumberOfUsers => handleMonitorNumberOfWebUsers(msg)
case msg: MonitorNumberOfUsers => handleMonitorNumberOfUsers(msg)
case msg: ValidateAuthToken => handleValidateAuthToken(msg)
case msg: RegisterUser => handleRegisterUser(msg)
case msg: UserJoinedVoiceConfMessage => handleUserJoinedVoiceConfMessage(msg)
@ -302,7 +302,12 @@ class MeetingActor(val mProps: MeetingProperties,
}
}
def handleMonitorNumberOfWebUsers(msg: MonitorNumberOfUsers) {
def handleMonitorNumberOfUsers(msg: MonitorNumberOfUsers) {
monitorNumberOfWebUsers()
monitorNumberOfUsers()
}
def monitorNumberOfWebUsers() {
if (Users.numWebUsers(liveMeeting.users) == 0 && liveMeeting.meetingModel.lastWebUserLeftOn > 0) {
if (liveMeeting.timeNowInMinutes - liveMeeting.meetingModel.lastWebUserLeftOn > 2) {
log.info("Empty meeting. Ejecting all users from voice. meetingId={}", mProps.meetingID)
@ -311,6 +316,12 @@ class MeetingActor(val mProps: MeetingProperties,
}
}
def monitorNumberOfUsers() {
val hasUsers = Users.numUsers(liveMeeting.users) != 0
// TODO: We could use a better control over this message to send it just when it really matters :)
eventBus.publish(BigBlueButtonEvent(mProps.meetingID, UpdateMeetingExpireMonitor(mProps.meetingID, hasUsers)))
}
def handleSendTimeRemainingUpdate(msg: SendTimeRemainingUpdate) {
if (mProps.duration > 0) {
val endMeetingTime = liveMeeting.meetingModel.startedOn + (mProps.duration * 60)

View File

@ -55,11 +55,32 @@ class MeetingActorInternal(val mProps: MeetingProperties,
time
}
private def getExpireNeverJoined(): Int = {
val time = expireNeverJoined
log.debug("ExpireNeverJoined: {} seconds", time)
time
}
private def getExpireLastUserLeft(): Int = {
val time = expireLastUserLeft
log.debug("ExpireLastUserLeft: {} seconds", time)
time
}
private val MonitorFrequency = 10 seconds
private val InactivityDeadline = FiniteDuration(getInactivityDeadline(), "seconds")
private val InactivityTimeLeft = FiniteDuration(getInactivityTimeLeft(), "seconds")
private val MonitorFrequency = 10 seconds
private var deadline = InactivityDeadline.fromNow
private var inactivity = InactivityDeadline.fromNow
private var inactivityWarning: Deadline = null
private val ExpireMeetingDuration = FiniteDuration(mProps.duration, "minutes")
private val ExpireMeetingNeverJoined = FiniteDuration(getExpireNeverJoined(), "seconds")
private val ExpireMeetingLastUserLeft = FiniteDuration(getExpireLastUserLeft(), "seconds")
private var meetingExpire = ExpireMeetingNeverJoined.fromNow
// Zero minutes means the meeting has no duration control
private var meetingDuration: Deadline = if (ExpireMeetingDuration > (0 minutes)) ExpireMeetingDuration.fromNow else null
context.system.scheduler.schedule(5 seconds, MonitorFrequency, self, "Monitor")
// Query to get voice conference users
@ -74,12 +95,14 @@ class MeetingActorInternal(val mProps: MeetingProperties,
def receive = {
case "Monitor" => handleMonitor()
case msg: UpdateMeetingExpireMonitor => handleUpdateMeetingExpireMonitor(msg)
case msg: Object => handleMessage(msg)
}
def handleMonitor() {
handleMonitorActivity()
handleMonitorNumberOfWebUsers()
handleMonitorExpiration()
}
def handleMessage(msg: Object) {
@ -102,12 +125,12 @@ class MeetingActorInternal(val mProps: MeetingProperties,
}
private def handleMonitorActivity() {
if (deadline.isOverdue() && inactivityWarning != null && inactivityWarning.isOverdue()) {
if (inactivity.isOverdue() && inactivityWarning != null && inactivityWarning.isOverdue()) {
log.info("Closing meeting {} due to inactivity for {} seconds", mProps.meetingID, InactivityDeadline.toSeconds)
updateInactivityMonitors()
eventBus.publish(BigBlueButtonEvent(mProps.meetingID, EndMeeting(mProps.meetingID)))
// Or else make sure to send only one warning message
} else if (deadline.isOverdue() && inactivityWarning == null) {
} else if (inactivity.isOverdue() && inactivityWarning == null) {
log.info("Sending inactivity warning to meeting {}", mProps.meetingID)
outGW.send(new InactivityWarning(mProps.meetingID, InactivityTimeLeft.toSeconds))
// We add 5 seconds so clients will have enough time to process the message
@ -115,8 +138,38 @@ class MeetingActorInternal(val mProps: MeetingProperties,
}
}
private def handleMonitorExpiration() {
if (meetingExpire != null && meetingExpire.isOverdue()) {
// User related meeting expiration methods
log.debug("Meeting {} expired. No users", mProps.meetingID)
meetingExpire = null
eventBus.publish(BigBlueButtonEvent(mProps.meetingID, EndMeeting(mProps.meetingID)))
} else if (meetingDuration != null && meetingDuration.isOverdue()) {
// Default meeting duration
meetingDuration = null
log.debug("Meeting {} expired. Reached it's fixed duration of {}", mProps.meetingID, ExpireMeetingDuration.toString())
eventBus.publish(BigBlueButtonEvent(mProps.meetingID, EndMeeting(mProps.meetingID)))
}
}
private def handleUpdateMeetingExpireMonitor(msg: UpdateMeetingExpireMonitor) {
if (msg.hasUser) {
if (meetingExpire != null) {
// User joined. Forget about this expiration for now
log.debug("Meeting has users. Stopping expiration for meeting {}", mProps.meetingID)
meetingExpire = null
}
} else {
if (meetingExpire == null) {
// User list is empty. Start this meeting expiration method
log.debug("Meeting has no users. Starting {} expiration for meeting {}", ExpireMeetingLastUserLeft.toString(), mProps.meetingID)
meetingExpire = ExpireMeetingLastUserLeft.fromNow
}
}
}
private def updateInactivityMonitors() {
deadline = InactivityDeadline.fromNow
inactivity = InactivityDeadline.fromNow
inactivityWarning = null
}

View File

@ -27,8 +27,6 @@ import org.apache.commons.lang3.RandomStringUtils;
public class Meeting {
private static final long MILLIS_IN_A_MINUTE = 60000;
private String name;
private String extMeetingId;
private String intMeetingId;
@ -65,8 +63,6 @@ public class Meeting {
private final Boolean isBreakout;
private final List<String> breakoutRooms = new ArrayList();
private long lastUserLeftOn = 0;
public Meeting(Builder builder) {
name = builder.name;
extMeetingId = builder.externalId;
@ -293,7 +289,6 @@ public class Meeting {
public User userLeft(String userid){
User u = (User) users.remove(userid);
if (users.isEmpty()) lastUserLeftOn = System.currentTimeMillis();
return u;
}
@ -317,54 +312,6 @@ public class Meeting {
public String getDialNumber() {
return dialNumber;
}
public boolean wasNeverJoined(int expiry) {
return (hasStarted() && !hasEnded() && nobodyJoined(expiry));
}
private boolean meetingInfinite() {
/* Meeting stays runs infinitely */
return duration == 0;
}
private boolean nobodyJoined(int expiry) {
if (expiry == 0) return false; /* Meeting stays created infinitely */
long now = System.currentTimeMillis();
return (!userHasJoined && (now - createdTime) > (expiry * MILLIS_IN_A_MINUTE));
}
private boolean hasBeenEmptyFor(int expiry) {
long now = System.currentTimeMillis();
return (now - lastUserLeftOn > (expiry * MILLIS_IN_A_MINUTE));
}
private boolean isEmpty() {
return users.isEmpty();
}
public boolean hasExpired(int expiry) {
return (hasStarted() && userHasJoined && isEmpty() && hasBeenEmptyFor(expiry));
}
public boolean hasExceededDuration() {
return (hasStarted() && !hasEnded() && pastDuration());
}
private boolean pastDuration() {
if (meetingInfinite()) return false;
long now = System.currentTimeMillis();
return (now - startTime > (duration * MILLIS_IN_A_MINUTE));
}
private boolean hasStarted() {
return startTime > 0;
}
private boolean hasEnded() {
return endTime > 0;
}
public int getNumListenOnly() {
int sum = 0;

View File

@ -1341,21 +1341,6 @@ check_state() {
fi
fi
if grep -q removeMeetingWhenEnded=false $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties; then
echo "# Warning: In"
echo "#"
echo "# $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties"
echo "#"
echo "# detected the setting"
echo "#"
echo "# removeMeetingWhenEnded=false"
echo "#"
echo "# You should set this value to true. It enables bbb-web to immediately purge a meeting from"
echo "# memory when receiving an end API call. Otherwise, users must wait about 2 minutes"
echo "# request before creating a meeting with the same meetingID but with different parameters."
echo
fi
if (( $MEM < 3940 )); then
echo "# Warning: You are running BigBlueButton on a server with less than 4G of memory. Your"
echo "# performance may suffer."

View File

@ -115,19 +115,6 @@ defaultMaxUsers=0
# Current default is 0 (meeting doesn't end).
defaultMeetingDuration=0
# Remove the meeting from memory when the end API is called.
# This allows 3rd-party apps to recycle the meeting right-away
# instead of waiting for the meeting to expire (see below).
removeMeetingWhenEnded=true
# The number of minutes before the system removes the meeting from memory.
defaultMeetingExpireDuration=1
# The number of minutes the system waits when a meeting is created and when
# a user joins. If after this period, a user hasn't joined, the meeting is
# removed from memory.
defaultMeetingCreateJoinDuration=5
# Disable recording by default.
# true - don't record even if record param in the api call is set to record
# false - when record param is passed from api, override this default

View File

@ -31,8 +31,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="redisStorageService" ref="redisStorageService"/>
</bean>
<bean id="expiredMeetingCleanupTimerTask" class="org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask"/>
<bean id="registeredUserCleanupTimerTask" class="org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask"/>
<bean id="keepAliveService" class="org.bigbluebutton.web.services.KeepAliveService"
@ -42,10 +40,6 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</bean>
<bean id="meetingService" class="org.bigbluebutton.api.MeetingService" init-method="start" destroy-method="stop">
<property name="defaultMeetingExpireDuration" value="${defaultMeetingExpireDuration}"/>
<property name="defaultMeetingCreateJoinDuration" value="${defaultMeetingCreateJoinDuration}"/>
<property name="removeMeetingWhenEnded" value="${removeMeetingWhenEnded}"/>
<property name="expiredMeetingCleanupTimerTask" ref="expiredMeetingCleanupTimerTask"/>
<property name="messagingService" ref="messagingService"/>
<property name="recordingService" ref="recordingService"/>
<property name="presDownloadService" ref="presDownloadService"/>

View File

@ -51,7 +51,6 @@ import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
import org.bigbluebutton.api.messaging.messages.MeetingEnded;
import org.bigbluebutton.api.messaging.messages.MeetingStarted;
import org.bigbluebutton.api.messaging.messages.RegisterUser;
import org.bigbluebutton.api.messaging.messages.RemoveExpiredMeetings;
import org.bigbluebutton.api.messaging.messages.UserJoined;
import org.bigbluebutton.api.messaging.messages.UserJoinedVoice;
import org.bigbluebutton.api.messaging.messages.UserLeft;
@ -63,7 +62,6 @@ import org.bigbluebutton.api.messaging.messages.UserStatusChanged;
import org.bigbluebutton.api.messaging.messages.UserUnsharedWebcam;
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
import org.bigbluebutton.api.messaging.messages.StunTurnInfoRequested;
import org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask;
import org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask;
import org.bigbluebutton.web.services.turn.StunServer;
import org.bigbluebutton.web.services.turn.StunTurnService;
@ -90,14 +88,10 @@ public class MeetingService implements MessageListener {
private final ConcurrentMap<String, Meeting> meetings;
private final ConcurrentMap<String, UserSession> sessions;
private int defaultMeetingExpireDuration = 1;
private int defaultMeetingCreateJoinDuration = 5;
private RecordingService recordingService;
private MessagingService messagingService;
private ExpiredMeetingCleanupTimerTask cleaner;
private RegisteredUserCleanupTimerTask registeredUserCleaner;
private StunTurnService stunTurnService;
private boolean removeMeetingWhenEnded = false;
private ParamsProcessorUtil paramsProcessorUtil;
private PresentationUrlDownloadService presDownloadService;
@ -131,13 +125,6 @@ public class MeetingService implements MessageListener {
return user;
}
/**
* Remove the meetings that have ended from the list of running meetings.
*/
public void removeExpiredMeetings() {
handle(new RemoveExpiredMeetings());
}
/**
* Remove registered users who did not successfully joined the meeting.
*/
@ -162,7 +149,6 @@ public class MeetingService implements MessageListener {
}
}
}
handle(new RemoveExpiredMeetings());
}
private void kickOffProcessingOfRecording(Meeting m) {
@ -203,77 +189,6 @@ public class MeetingService implements MessageListener {
}
}
private void checkAndRemoveExpiredMeetings() {
for (Meeting m : meetings.values()) {
if (m.hasExpired(defaultMeetingExpireDuration)) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
logData.put("name", m.getName());
logData.put("event", "removing_meeting");
logData.put("description", "Meeting has expired.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("Removing expired meeting: data={}", logStr);
processMeetingForRemoval(m);
continue;
}
if (m.isForciblyEnded()) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
logData.put("name", m.getName());
logData.put("event", "removing_meeting");
logData.put("description", "Meeting forcefully ended.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("Removing ended meeting: data={}", logStr);
processMeetingForRemoval(m);
continue;
}
if (m.wasNeverJoined(defaultMeetingCreateJoinDuration)) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
logData.put("name", m.getName());
logData.put("event", "removing_meeting");
logData.put("description", "Meeting has not been joined.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("Removing un-joined meeting: data={}", logStr);
destroyMeeting(m.getInternalId());
meetings.remove(m.getInternalId());
removeUserSessions(m.getInternalId());
continue;
}
if (m.hasExceededDuration()) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
logData.put("name", m.getName());
logData.put("event", "removing_meeting");
logData.put("description", "Meeting exceeded duration.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("Removing past duration meeting: data={}", logStr);
endMeeting(m.getInternalId());
}
}
}
private void destroyMeeting(String meetingID) {
messagingService.destroyMeeting(meetingID);
}
@ -608,12 +523,10 @@ public class MeetingService implements MessageListener {
Meeting m = getMeeting(message.meetingId);
if (m != null) {
m.setForciblyEnded(true);
if (removeMeetingWhenEnded) {
processRecording(m.getInternalId());
destroyMeeting(m.getInternalId());
meetings.remove(m.getInternalId());
removeUserSessions(m.getInternalId());
}
processRecording(m.getInternalId());
destroyMeeting(m.getInternalId());
meetings.remove(m.getInternalId());
removeUserSessions(m.getInternalId());
}
}
@ -947,8 +860,6 @@ public class MeetingService implements MessageListener {
userSharedWebcam((UserSharedWebcam) message);
} else if (message instanceof UserUnsharedWebcam) {
userUnsharedWebcam((UserUnsharedWebcam) message);
} else if (message instanceof RemoveExpiredMeetings) {
checkAndRemoveExpiredMeetings();
} else if (message instanceof CreateMeeting) {
processCreateMeeting((CreateMeeting) message);
} else if (message instanceof EndMeeting) {
@ -997,18 +908,9 @@ public class MeetingService implements MessageListener {
public void stop() {
processMessage = false;
cleaner.stop();
registeredUserCleaner.stop();
}
public void setDefaultMeetingCreateJoinDuration(int expiration) {
this.defaultMeetingCreateJoinDuration = expiration;
}
public void setDefaultMeetingExpireDuration(int meetingExpiration) {
this.defaultMeetingExpireDuration = meetingExpiration;
}
public void setRecordingService(RecordingService s) {
recordingService = s;
}
@ -1017,17 +919,6 @@ public class MeetingService implements MessageListener {
messagingService = mess;
}
public void setExpiredMeetingCleanupTimerTask(
ExpiredMeetingCleanupTimerTask c) {
cleaner = c;
cleaner.setMeetingService(this);
cleaner.start();
}
public void setRemoveMeetingWhenEnded(boolean s) {
removeMeetingWhenEnded = s;
}
public void setRegisteredUserCleanupTimerTask(
RegisteredUserCleanupTimerTask c) {
registeredUserCleaner = c;

View File

@ -1,5 +0,0 @@
package org.bigbluebutton.api.messaging.messages;
public class RemoveExpiredMeetings implements IMessage {
}

View File

@ -1,55 +0,0 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.web.services;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.bigbluebutton.api.MeetingService;
public class ExpiredMeetingCleanupTimerTask {
private MeetingService service;
private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
private long runEvery = 60000;
public void setMeetingService(MeetingService svc) {
this.service = svc;
}
public void start() {
scheduledThreadPool.scheduleWithFixedDelay(new CleanupTask(), 60000, runEvery, TimeUnit.MILLISECONDS);
}
public void stop() {
scheduledThreadPool.shutdownNow();
}
public void setRunEvery(long v) {
runEvery = v;
}
private class CleanupTask implements Runnable {
public void run() {
service.removeExpiredMeetings();
}
}
}