fix (rooms): Harden against race conditions.
Add locking to room init, turnover, and cancellation. Add cleanup of nonexistent members in slot init. Fix an issue where members were being charged for cancelling rooms.
This commit is contained in:
@@ -37,7 +37,7 @@ class SlotMember:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def member(self):
|
def member(self):
|
||||||
return self.guild.get_member(self.data.userid)
|
return self.guild.get_member(self.userid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_attended(self):
|
def has_attended(self):
|
||||||
@@ -254,6 +254,11 @@ class TimeSlot:
|
|||||||
Adds the TimeSlot to cache.
|
Adds the TimeSlot to cache.
|
||||||
Returns the (channelid, messageid).
|
Returns the (channelid, messageid).
|
||||||
"""
|
"""
|
||||||
|
# Cleanup any non-existent members
|
||||||
|
for memid, mem in list(self.members.items()):
|
||||||
|
if not mem.data or not mem.member:
|
||||||
|
self.members.pop(memid)
|
||||||
|
|
||||||
# Calculate overwrites
|
# Calculate overwrites
|
||||||
overwrites = {
|
overwrites = {
|
||||||
mem.member: self._member_overwrite
|
mem.member: self._member_overwrite
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ from data.conditions import GEQ
|
|||||||
from .module import module
|
from .module import module
|
||||||
from .lib import utc_now
|
from .lib import utc_now
|
||||||
from .tracker import AccountabilityGuild as AGuild
|
from .tracker import AccountabilityGuild as AGuild
|
||||||
|
from .tracker import room_lock
|
||||||
from .TimeSlot import SlotMember
|
from .TimeSlot import SlotMember
|
||||||
from .data import accountability_members, accountability_member_info, accountability_open_slots, accountability_rooms
|
from .data import accountability_members, accountability_member_info, accountability_rooms
|
||||||
|
|
||||||
|
|
||||||
@module.cmd(
|
@module.cmd(
|
||||||
@@ -104,29 +105,30 @@ async def cmd_rooms(ctx):
|
|||||||
cost = len(to_cancel) * ctx.guild_settings.accountability_price.value
|
cost = len(to_cancel) * ctx.guild_settings.accountability_price.value
|
||||||
|
|
||||||
slotids = [row['slotid'] for row in to_cancel]
|
slotids = [row['slotid'] for row in to_cancel]
|
||||||
accountability_members.delete_where(
|
async with room_lock:
|
||||||
userid=ctx.author.id,
|
accountability_members.delete_where(
|
||||||
slotid=slotids
|
userid=ctx.author.id,
|
||||||
)
|
slotid=slotids
|
||||||
|
)
|
||||||
|
|
||||||
# Handle case where the slot has already opened
|
# Handle case where the slot has already opened
|
||||||
for row in to_cancel:
|
for row in to_cancel:
|
||||||
aguild = AGuild.cache.get(row['guildid'], None)
|
aguild = AGuild.cache.get(row['guildid'], None)
|
||||||
if aguild:
|
if aguild and aguild.upcoming_slot and aguild.upcoming_slot.data:
|
||||||
if aguild.upcoming_slot and aguild.upcoming_slot.data and (aguild.upcoming_slot.data.slotid in slotids):
|
if aguild.upcoming_slot.data.slotid in slotids:
|
||||||
aguild.upcoming_slot.members.pop(ctx.author.id, None)
|
aguild.upcoming_slot.members.pop(ctx.author.id, None)
|
||||||
if aguild.upcoming_slot.channel:
|
if aguild.upcoming_slot.channel:
|
||||||
try:
|
try:
|
||||||
await aguild.upcoming_slot.channel.set_permissions(
|
await aguild.upcoming_slot.channel.set_permissions(
|
||||||
ctx.author,
|
ctx.author,
|
||||||
overwrite=None
|
overwrite=None
|
||||||
)
|
)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
await aguild.upcoming_slot.update_status()
|
await aguild.upcoming_slot.update_status()
|
||||||
break
|
break
|
||||||
|
|
||||||
ctx.alion.addCoins(-cost)
|
ctx.alion.addCoins(cost)
|
||||||
await ctx.embed_reply(
|
await ctx.embed_reply(
|
||||||
"Successfully canceled your bookings."
|
"Successfully canceled your bookings."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,19 @@ from .module import module
|
|||||||
|
|
||||||
|
|
||||||
voice_ignore_lock = asyncio.Lock()
|
voice_ignore_lock = asyncio.Lock()
|
||||||
|
room_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def locker(lock):
|
||||||
|
"""
|
||||||
|
Function decorator to wrap the function in a provided Lock
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
async def wrapped(*args, **kwargs):
|
||||||
|
async with lock:
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class AccountabilityGuild:
|
class AccountabilityGuild:
|
||||||
@@ -184,7 +197,7 @@ async def turnover():
|
|||||||
)
|
)
|
||||||
for slot in current_slots
|
for slot in current_slots
|
||||||
for mem in slot.members.values()
|
for mem in slot.members.values()
|
||||||
if mem.member.voice and mem.member.voice.channel != slot.channel
|
if mem.data and mem.member and mem.member.voice and mem.member.voice.channel != slot.channel
|
||||||
)
|
)
|
||||||
# We return exceptions here to ignore any permission issues that occur with moving members.
|
# We return exceptions here to ignore any permission issues that occur with moving members.
|
||||||
# It's also possible (likely) that members will move while we are moving other members
|
# It's also possible (likely) that members will move while we are moving other members
|
||||||
@@ -279,7 +292,8 @@ async def _accountability_loop():
|
|||||||
next_time = next_time + datetime.timedelta(minutes=5)
|
next_time = next_time + datetime.timedelta(minutes=5)
|
||||||
# Open next sessions
|
# Open next sessions
|
||||||
try:
|
try:
|
||||||
await open_next(next_time)
|
async with room_lock:
|
||||||
|
await open_next(next_time)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Unknown exception. Catch it so the loop doesn't die.
|
# Unknown exception. Catch it so the loop doesn't die.
|
||||||
client.log(
|
client.log(
|
||||||
@@ -293,7 +307,8 @@ async def _accountability_loop():
|
|||||||
elif next_time.minute == 0:
|
elif next_time.minute == 0:
|
||||||
# Start new sessions
|
# Start new sessions
|
||||||
try:
|
try:
|
||||||
await turnover()
|
async with room_lock:
|
||||||
|
await turnover()
|
||||||
except Exception:
|
except Exception:
|
||||||
# Unknown exception. Catch it so the loop doesn't die.
|
# Unknown exception. Catch it so the loop doesn't die.
|
||||||
client.log(
|
client.log(
|
||||||
|
|||||||
Reference in New Issue
Block a user