Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-54157] Fix potential nil access upon starting a recording #495

Merged
merged 1 commit into from
Aug 21, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions server/recording_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ func (p *Plugin) recJobTimeoutChecker(callID, jobID string) {
// If the recording hasn't started (bot hasn't joined yet) we notify the
// client.
if recState.StartAt == 0 {
if recState.JobID != jobID {
p.LogInfo("a new job has started in between, exiting", "callID", callID, "jobID", jobID)
return
}
Comment on lines +39 to +42
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an extra check to avoid making potentially inconsistent state updates due to previously failed jobs. Ideally we'd cancel this timeout check whenever we stop a recording but the synchronization needed would make the code more complex so it seems like a few extra goroutines that return early are not too bad.


p.LogError("timed out waiting for recorder bot to join", "callID", callID, "jobID", jobID)

state.Call.Recording = nil
Expand Down Expand Up @@ -78,7 +83,7 @@ func (p *Plugin) handleRecordingStartAction(state *channelState, callID, userID

defer func() {
// In case of any error we relay it to the client.
if res.Err != "" {
if res.Err != "" && recState != nil {
recState.EndAt = time.Now().UnixMilli()
recState.Err = res.Err
p.publishWebSocketEvent(wsEventCallRecordingState, map[string]interface{}{
Expand Down Expand Up @@ -113,6 +118,7 @@ func (p *Plugin) handleRecordingStartAction(state *channelState, callID, userID
res.Code = http.StatusInternalServerError
return nil, res
}

if jobErr != nil {
state.Call.Recording = nil
if err := p.kvSetChannelState(callID, state); err != nil {
Expand Down Expand Up @@ -185,29 +191,23 @@ func (p *Plugin) handleRecordingStopAction(state *channelState, callID string) (
}
}()

// Sending the event prior to making the API call to the job service
// since it could take a few seconds to complete but we want clients
// to get their local state updated as soon as it changes on the server.
p.publishWebSocketEvent(wsEventCallRecordingState, map[string]interface{}{
"callID": callID,
"recState": recState.getClientState().toMap(),
}, &model.WebsocketBroadcast{ChannelId: callID, ReliableClusterSend: true})

// We don't want to keep the lock while making the API call to the service since it
// could take a while to return.
p.unlockCall(callID)
Comment on lines -188 to -198
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to the panic but since we moved to the new job API we don't need to do this, the stop action is completely asynchronous and safe to be called under lock.

if err := p.getJobService().StopJob(callID); err != nil {
res.Err = "failed to stop recording job: " + err.Error()
res.Code = http.StatusInternalServerError
return nil, res
}

p.publishWebSocketEvent(wsEventCallRecordingState, map[string]interface{}{
"callID": callID,
"recState": recState.getClientState().toMap(),
}, &model.WebsocketBroadcast{ChannelId: callID, ReliableClusterSend: true})

return recState.getClientState(), res
}

func (p *Plugin) handleRecordingAction(w http.ResponseWriter, r *http.Request, callID, action string) {
var res httpResponse
defer p.httpAudit("handlePostRecording", &res, w, r)
defer p.httpAudit("handleRecordingAction", &res, w, r)

userID := r.Header.Get("Mattermost-User-Id")

Expand Down