3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-20 16:25:03 +00:00

Merge pull request #7 from Dragory/master

pull
This commit is contained in:
DEX 2021-04-14 03:50:53 +05:00 committed by GitHub
commit 07e4f53170
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 40 deletions

View file

@ -12,12 +12,15 @@ import { CounterTriggerState } from "./entities/CounterTriggerState";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils"; import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
import { connection } from "./db"; import { connection } from "./db";
import { Queue } from "../Queue";
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS; const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS; const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
const decayQueue = new Queue();
async function deleteCountersMarkedToBeDeleted(): Promise<void> { async function deleteCountersMarkedToBeDeleted(): Promise<void> {
await getRepository(Counter) await getRepository(Counter)
.createQueryBuilder() .createQueryBuilder()
@ -158,51 +161,55 @@ export class GuildCounters extends BaseGuildRepository {
); );
} }
async decay(id: number, decayPeriodMs: number, decayAmount: number) { decay(id: number, decayPeriodMs: number, decayAmount: number) {
const counter = (await this.counters.findOne({ return decayQueue.add(async () => {
where: { const counter = (await this.counters.findOne({
id, where: {
}, id,
}))!; },
}))!;
const diffFromLastDecayMs = moment.utc().diff(moment.utc(counter.last_decay_at!), "ms"); const diffFromLastDecayMs = moment.utc().diff(moment.utc(counter.last_decay_at!), "ms");
if (diffFromLastDecayMs < decayPeriodMs) { if (diffFromLastDecayMs < decayPeriodMs) {
return; return;
} }
const decayAmountToApply = Math.round((diffFromLastDecayMs / decayPeriodMs) * decayAmount); const decayAmountToApply = Math.round((diffFromLastDecayMs / decayPeriodMs) * decayAmount);
if (decayAmountToApply === 0) { if (decayAmountToApply === 0) {
return; return;
} }
// Calculate new last_decay_at based on the rounded decay amount we applied. This makes it so that over time, the decayed amount will stay accurate, even if we round some here. // Calculate new last_decay_at based on the rounded decay amount we applied. This makes it so that over time, the decayed amount will stay accurate, even if we round some here.
const newLastDecayDate = moment const newLastDecayDate = moment
.utc(counter.last_decay_at) .utc(counter.last_decay_at)
.add((decayAmountToApply / decayAmount) * decayPeriodMs, "ms") .add((decayAmountToApply / decayAmount) * decayPeriodMs, "ms")
.format(DBDateFormat); .format(DBDateFormat);
const rawUpdate = const rawUpdate =
decayAmountToApply >= 0 decayAmountToApply >= 0
? `GREATEST(value - ${decayAmountToApply}, 0)` ? `GREATEST(value - ${decayAmountToApply}, 0)`
: `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`; : `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`;
await this.counterValues.update( // Using an UPDATE with ORDER BY in an attempt to avoid deadlocks from simultaneous decays
{ // Also see https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html
counter_id: id, await this.counterValues
}, .createQueryBuilder("CounterValue")
{ .where("counter_id = :id", { id })
value: () => rawUpdate, .orderBy("id")
}, .update({
); value: () => rawUpdate,
})
.execute();
await this.counters.update( await this.counters.update(
{ {
id, id,
}, },
{ {
last_decay_at: newLastDecayDate, last_decay_at: newLastDecayDate,
}, },
); );
});
} }
async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) { async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {

View file

@ -27,7 +27,15 @@ export const TRegex = new t.Type<RegExp, string>(
(s): s is RegExp => s instanceof RegExp, (s): s is RegExp => s instanceof RegExp,
(from, to) => (from, to) =>
either.chain(t.string.validate(from, to), s => { either.chain(t.string.validate(from, to), s => {
return t.success(inputPatternToRegExp(s)); try {
return t.success(inputPatternToRegExp(s));
} catch (err) {
if (err instanceof InvalidRegexError) {
return t.failure(s, [], err.message);
}
throw err;
}
}), }),
s => `/${s.source}/${s.flags}`, s => `/${s.source}/${s.flags}`,
); );