Merge pull request #226 from ssb-ngi-pointer/graceful-dashboard-timeout

fix potential deadlocks
This commit is contained in:
Henry 2021-05-31 10:45:31 +02:00 committed by GitHub
commit 0563e9bf3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 19 deletions

View File

@ -85,11 +85,12 @@ func (m *Manager) AddEndpoint(who refs.FeedRef, edp muxrpc.Endpoint) {
m.roomMu.Lock() m.roomMu.Lock()
// add ref to to the room map // add ref to to the room map
m.room[who.Ref()] = edp m.room[who.Ref()] = edp
currentMembers := m.room.AsList()
m.roomMu.Unlock()
// update all the connected tunnel.endpoints calls // update all the connected tunnel.endpoints calls
m.endpointsUpdater.Update(m.room.AsList()) m.endpointsUpdater.Update(currentMembers)
// update all the connected room.attendants calls // update all the connected room.attendants calls
m.attendantsUpdater.Joined(who) m.attendantsUpdater.Joined(who)
m.roomMu.Unlock()
} }
// Remove removes the peer from the room // Remove removes the peer from the room
@ -97,11 +98,12 @@ func (m *Manager) Remove(who refs.FeedRef) {
m.roomMu.Lock() m.roomMu.Lock()
// remove ref from lobby // remove ref from lobby
delete(m.room, who.Ref()) delete(m.room, who.Ref())
currentMembers := m.room.AsList()
m.roomMu.Unlock()
// update all the connected tunnel.endpoints calls // update all the connected tunnel.endpoints calls
m.endpointsUpdater.Update(m.room.AsList()) m.endpointsUpdater.Update(currentMembers)
// update all the connected room.attendants calls // update all the connected room.attendants calls
m.attendantsUpdater.Left(who) m.attendantsUpdater.Left(who)
m.roomMu.Unlock()
} }
// AlreadyAdded returns true if the peer was already added to the room. // AlreadyAdded returns true if the peer was already added to the room.
@ -109,18 +111,22 @@ func (m *Manager) Remove(who refs.FeedRef) {
func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool { func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool {
m.roomMu.Lock() m.roomMu.Lock()
var currentMembers []string
// if the peer didn't call tunnel.announce() // if the peer didn't call tunnel.announce()
_, has := m.room[who.Ref()] _, has := m.room[who.Ref()]
if !has { if !has {
// register them as if they didnt // register them as if they didnt
m.room[who.Ref()] = edp m.room[who.Ref()] = edp
currentMembers = m.room.AsList()
}
m.roomMu.Unlock()
if !has {
// update everyone // update everyone
m.endpointsUpdater.Update(m.room.AsList()) m.endpointsUpdater.Update(currentMembers)
m.attendantsUpdater.Joined(who) m.attendantsUpdater.Joined(who)
} }
m.roomMu.Unlock()
return has return has
} }

View File

@ -5,8 +5,11 @@ package admin
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"go.mindeco.de/http/render" "go.mindeco.de/http/render"
"go.mindeco.de/log/level"
"go.mindeco.de/logging"
refs "go.mindeco.de/ssb-refs" refs "go.mindeco.de/ssb-refs"
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network" "github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
@ -25,36 +28,53 @@ type dashboardHandler struct {
} }
func (h dashboardHandler) overview(w http.ResponseWriter, req *http.Request) (interface{}, error) { func (h dashboardHandler) overview(w http.ResponseWriter, req *http.Request) (interface{}, error) {
roomRef := h.netInfo.RoomID.Ref() var (
ctx = req.Context()
roomRef = h.netInfo.RoomID.Ref()
onlineRefs := h.roomState.List() onlineRefs []refs.FeedRef
refsUpdateCh = make(chan []refs.FeedRef)
onlineCount = -1
)
// this is an attempt to sidestep the _dashboard doesn't render_ bug (issue #210)
// first we retreive the member state via a goroutine in the background
go func() {
refsUpdateCh <- h.roomState.ListAsRefs()
}()
// if it doesn't complete in 10 seconds the slice stays empty and onlineCount remains -1 (to indicate a problem)
select {
case <-time.After(10 * time.Second):
logger := logging.FromContext(ctx)
level.Warn(logger).Log("event", "didnt retreive room state in time")
case onlineRefs = <-refsUpdateCh:
onlineCount = len(onlineRefs)
}
// in the timeout case, nothing will happen here since the onlineRefs slice is empty
onlineMembers := make([]roomdb.Member, len(onlineRefs)) onlineMembers := make([]roomdb.Member, len(onlineRefs))
for i := range onlineRefs { for i, ref := range onlineRefs {
ref, err := refs.ParseFeedRef(onlineRefs[i]) var err error
if err != nil { onlineMembers[i], err = h.dbs.Members.GetByFeed(ctx, ref)
return nil, fmt.Errorf("failed to parse online ref: %w", err)
}
onlineMembers[i], err = h.dbs.Members.GetByFeed(req.Context(), *ref)
if err != nil { if err != nil {
// TODO: do we want to show "external users" (non-members) on the dashboard? // TODO: do we want to show "external users" (non-members) on the dashboard?
return nil, fmt.Errorf("failed to lookup online member: %w", err) return nil, fmt.Errorf("failed to lookup online member: %w", err)
} }
} }
onlineCount := len(onlineMembers) memberCount, err := h.dbs.Members.Count(ctx)
memberCount, err := h.dbs.Members.Count(req.Context())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count members: %w", err) return nil, fmt.Errorf("failed to count members: %w", err)
} }
inviteCount, err := h.dbs.Invites.Count(req.Context()) inviteCount, err := h.dbs.Invites.Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count invites: %w", err) return nil, fmt.Errorf("failed to count invites: %w", err)
} }
deniedCount, err := h.dbs.DeniedKeys.Count(req.Context()) deniedCount, err := h.dbs.DeniedKeys.Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count denied keys: %w", err) return nil, fmt.Errorf("failed to count denied keys: %w", err)
} }