async def launch_shard(self, gateway, shard_id): async with RedisLock( self.redis, key="identify", timeout=200, # More than the connect timeout wait_timeout=None): log.info("Shard ID %s has acquired the IDENTIFY lock." % shard_id) return await super().launch_shard(gateway, shard_id)
async def invoke(self, ctx): if ctx.command is None: return if not ( ctx.command.name in CONCURRENCY_LIMITED_COMMANDS or (ctx.command.root_parent and ctx.command.root_parent.name in CONCURRENCY_LIMITED_COMMANDS) ): return await super().invoke(ctx) try: async with RedisLock(self.redis, f"command:{ctx.author.id}", 60, 1): return await super().invoke(ctx) except LockTimeoutError: await ctx.reply("You are currently running another command. Please wait and try again later.")
async def task(sleep: float): async with RedisLock(red, key, timeout=5): await asyncio.sleep(sleep) return True
async def test_renew_lock(red, key): async with RedisLock(red, key, timeout=10) as lock: await lock.renew(10) assert (await red.pttl(key)) > 9 * 1000
async def test_extend_lock(red, key): async with RedisLock(red, key, timeout=10) as lock: await lock.extend(10) assert (await red.ttl(key)) > 11
async def test_acquire_lock_timeout(red, key): await red.setex(key, 10, str(uuid.uuid4())) with pytest.raises(LockTimeoutError): async with RedisLock(red, key, wait_timeout=0) as lock: assert False, "Acquired lock when the key is set"
async def test_acquire_lock(redis_pool, key): async with RedisLock(redis_pool, key) as lock: assert await lock.is_owner()
async def tipgiveaway_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user # Check roles if not await self.role_check(msg): return # Punish them for trying to do this command in a no spam channel if msg.channel.id in config.Config.instance().get_no_spam_channels(): redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "You can't view donate to the giveaway in this channel") await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) await Messages.delete_message_if_ok(msg) return # Get their tip amount try: tip_amount = RegexUtil.find_float(msg.content) if tip_amount < Constants.TIP_MINIMUM: await Messages.send_error_dm(msg.author, f"Minimum tip amount if {Constants.TIP_MINIMUM}") await Messages.delete_message_if_ok(msg) return except AmountMissingException: await Messages.send_usage_dm(msg.author, TIPGIVEAWAY_INFO) await Messages.delete_message_if_ok(msg) return # Get active giveaway gw = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if gw is None: # get bot-pending giveaway or create one gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is None: try: # Initiate the bot giveaway with a lock to avoid race condition # Lock this so concurrent giveaways can't be started/avoid race condition async with RedisLock( await RedisDB.instance().get_redis(), key=f"{Env.currency_symbol().lower()}giveawaylock:{msg.guild.id}", timeout=30, wait_timeout=30 ) as lock: # See if giveaway already in progress should_create = False active_giveaway = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if active_giveaway is None: bot_giveaway = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if bot_giveaway is None: should_create = True if should_create: # Start giveaway async with in_transaction() as conn: gw = await Giveaway.start_giveaway_bot( server_id=msg.guild.id, entry_fee=config.Config.instance().get_giveaway_auto_fee(), started_in_channel=msg.channel.id, conn=conn ) except LockTimeoutError: gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is None: await Messages.send_error_dm(msg.author, "I was unable to process your donation, try again alter!") await Messages.delete_message_if_ok(msg) return # Check balance available_balance = Env.raw_to_amount(await user.get_available_balance()) if tip_amount > available_balance: if not ctx.god: redis_key = f"ticketspam:{msg.author.id}" spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "Your balance isn't high enough to complete this tip.") await Messages.delete_message_if_ok(msg) return # See if they already contributed user_tx = await Transaction.filter(giveaway__id=gw.id, sending_user__id=user.id).first() already_entered = False async with in_transaction() as conn: if user_tx is not None: if int(user_tx.amount) >= int(gw.entry_fee): already_entered=True user_tx.amount = str(int(user_tx.amount) + Env.amount_to_raw(tip_amount)) await user_tx.save(update_fields=['amount'], using_db=conn) else: user_tx = await Transaction.create_transaction_giveaway( user, tip_amount, gw, conn=conn ) if gw.end_at is None: if not already_entered and int(user_tx.amount) >= int(gw.entry_fee): await Messages.send_success_dm(msg.author, f"With your generous donation of {Env.raw_to_amount(int(user_tx.amount))} {Env.currency_symbol()} I have reserved your spot for giveaway #{gw.id}!") else: await Messages.send_success_dm(msg.author, f"Your generous donation of {Env.raw_to_amount(int(user_tx.amount))} {Env.currency_symbol()} will help support giveaway #{gw.id}!") # See if we should start this giveaway, and start it if so # TODO - We should use the DB SUM() function but,we store it as a VarChar and tortoise-orm currently doesn't support casting giveaway_sum_raw = 0 for tx in await Transaction.filter(giveaway=gw): giveaway_sum_raw += int(tx.amount) giveaway_sum = Env.raw_to_amount(giveaway_sum_raw) if giveaway_sum >= config.Config.instance().get_giveaway_auto_minimum(): # start giveaway # re-fetch latest version gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is not None: gw.end_at = datetime.datetime.utcnow() + datetime.timedelta(minutes=config.Config.instance().get_giveaway_auto_duration()) gw.started_in_channel = msg.channel.id async with in_transaction() as conn: await gw.save(update_fields=['end_at', 'started_in_channel'], using_db=conn) # Announce giveaway embed = self.format_giveaway_announcement(gw, amount=giveaway_sum_raw) await msg.channel.send(embed=embed) for ch in config.Config.instance().get_giveaway_announce_channels(): if ch != msg.channel.id: channel = msg.guild.get_channel(ch) if channel is not None: try: await channel.send(embed=embed) except Exception: pass # Start the timer asyncio.create_task(self.start_giveaway_timer(gw)) else: if not already_entered and int(user_tx.amount) >= int(gw.entry_fee): await Messages.send_success_dm(msg.author, f"With your generous donation of {tip_amount} {Env.currency_symbol()} I have entered you into giveaway #{gw.id}!") else: await Messages.send_success_dm(msg.author, f"Your generous donation of {tip_amount} {Env.currency_symbol()} will help support giveaway #{gw.id}!") if msg.channel.id in config.Config.instance().get_giveaway_no_delete_channels(): await Messages.add_tip_reaction(msg, tip_amount) await Messages.delete_message_if_ok(msg) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance().get_no_stats_channels(): await stats.update_tip_stats(tip_amount)
async def giveaway_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user # Check paused if await RedisDB.instance().is_paused(): await Messages.send_error_dm(msg.author, f"Transaction activity is currently suspended. I'll be back online soon!") return # Check roles if not await self.role_check(msg): return elif msg.channel.id in config.Config.instance().get_no_spam_channels(): await Messages.send_error_dm(msg.author, f"You can't start giveaways in this channel") return if 'fee=' not in msg.content or 'duration=' not in msg.content: await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) await Messages.add_x_reaction(msg) return # Parse message split_content = msg.content.split(' ') cleaned_content = msg.content for split in split_content: if split.startswith('fee='): cleaned_content.replace(split, "") split = split.replace('fee=','').strip() if not split: continue try: fee = abs(float(split)) except ValueError as e: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return elif split.startswith('duration='): cleaned_content.replace(split, "") split=split.replace('duration=','').strip() if not split: continue try: duration = abs(int(split)) if not ctx.god and (duration < config.Config.instance().get_giveaway_min_duration() or duration > config.Config.instance().get_giveaway_max_duration()): raise ValueError("Bad duration specified") except ValueError as e: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return # Find giveaway amount try: giveaway_amount = RegexUtil.find_float(cleaned_content) if Validators.too_many_decimals(giveaway_amount): await Messages.send_error_dm(ctx.message.author, f"You are only allowed to use {Env.precision_digits()} digits after the decimal for giveaway amount.") ctx.error = True return elif fee > giveaway_amount * config.Config.instance().get_giveaway_max_fee_multiplier(): await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return elif giveaway_amount < config.Config.instance().get_giveaway_minimum(): await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return except AmountMissingException: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return # See how much they need to make this tip. available_balance = Env.raw_to_amount(await user.get_available_balance()) if giveaway_amount > available_balance: await Messages.add_x_reaction(ctx.message) await Messages.send_error_dm(msg.author, f"Your balance isn't high enough to start this giveaway. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{giveaway_amount} {Env.currency_symbol()}**") return try: # Lock this so concurrent giveaways can't be started/avoid race condition async with RedisLock( await RedisDB.instance().get_redis(), key=f"{Env.currency_symbol().lower()}giveawaylock:{msg.guild.id}", timeout=30, wait_timeout=30 ) as lock: # See if giveaway already in progress active_giveaway = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if active_giveaway is not None: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "There's already a giveaway in progress on this server") return # Start giveaway async with in_transaction() as conn: gw = await Giveaway.start_giveaway_user( server_id=msg.guild.id, started_by=user, amount=giveaway_amount, entry_fee=fee, duration=duration, started_in_channel=msg.channel.id, conn=conn ) # Create pending TX for this user await Transaction.create_transaction_giveaway( sending_user=user, amount=giveaway_amount, giveaway=gw, conn=conn ) # Announce giveaway embed = self.format_giveaway_announcement(gw) await msg.channel.send(embed=embed) for ch in config.Config.instance().get_giveaway_announce_channels(): if ch != msg.channel.id: channel = msg.guild.get_channel(ch) if channel is not None: try: await channel.send(embed=embed) except Exception: pass # Start the timer asyncio.create_task(self.start_giveaway_timer(gw)) except LockTimeoutError: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "I couldn't start a giveaway, maybe someone else beat you to it as there can only be 1 active at a time.")
async def before_identify_hook(self, shard_id, *, initial=False): async with RedisLock(self.redis, key="identify", timeout=5, wait_timeout=None): await asyncio.sleep(5)
async def before_identify_hook(self, shard_id, *, initial=False): async with RedisLock(self.redis, f"identify:{shard_id % 16}", 5, None): await asyncio.sleep(5)