mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
fix(app): shrink stored favorite gifs and raise their budget (#2643)
This commit is contained in:
1 parent
5fea319f4e
commit
905af5dd5a
13 files changed
+191
-215
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import {DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES, decideOversizePush} from '@app/features/user/state/SyncedFieldBudget';
|
||||
import {verifyRoundtripStability} from '@app/features/user/state/SyncedFieldRoundtrip';
|
||||
import {
|
||||
createSyncedFieldMachineSnapshot,
|
||||
@@ -59,6 +60,7 @@ export async function makeSyncedField<
|
||||
}
|
||||
const isEnabled = (): boolean => config.enabled?.() ?? true;
|
||||
let machine = createSyncedFieldMachineSnapshot();
|
||||
let lastPreparedBytes: number | null = null;
|
||||
let ownerUserId: string | null = null;
|
||||
let observedUserId: string | null = null;
|
||||
const suspend = (reason: SyncedFieldFailureReason, message: string, error?: unknown): void => {
|
||||
@@ -130,13 +132,20 @@ export async function makeSyncedField<
|
||||
return;
|
||||
}
|
||||
const encodedBytes = toBinary(schema, candidate).length;
|
||||
if (encodedBytes > maxEncodedBytes) {
|
||||
const decision = decideOversizePush({encodedBytes, maxEncodedBytes, lastPreparedBytes});
|
||||
lastPreparedBytes = encodedBytes;
|
||||
if (decision === 'drop') {
|
||||
logger.error(
|
||||
`${tag}: payload of ${encodedBytes} bytes exceeds per-field budget of ${maxEncodedBytes}; push dropped.`,
|
||||
);
|
||||
transitionOnly({type: 'sync.localAlreadySynced'});
|
||||
return;
|
||||
}
|
||||
if (decision === 'push-shrinks') {
|
||||
logger.warn(
|
||||
`${tag}: payload of ${encodedBytes} bytes is over the per-field budget of ${maxEncodedBytes} but smaller than the previous candidate. Pushing so removals persist.`,
|
||||
);
|
||||
}
|
||||
const stability = verifyRoundtripStability<T, M>({
|
||||
schema,
|
||||
store,
|
||||
@@ -204,7 +213,7 @@ export async function makeSyncedField<
|
||||
processCommands();
|
||||
}
|
||||
await Promise.resolve();
|
||||
const maxEncodedBytes = config.maxEncodedBytes ?? 65_536;
|
||||
const maxEncodedBytes = config.maxEncodedBytes ?? DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES;
|
||||
const UserSettings = (await import('@app/features/user/state/UserSettings')).default;
|
||||
const SessionManager = (await import('@app/features/platform/state/AuthSession')).default;
|
||||
const applyDefaultsForUserChange = (userId: string): void => {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {decideOversizePush} from './SyncedFieldBudget';
|
||||
|
||||
const MAX = 1000;
|
||||
|
||||
describe('decideOversizePush', () => {
|
||||
it('accepts a payload inside the budget', () => {
|
||||
expect(decideOversizePush({encodedBytes: 999, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe(
|
||||
'within-budget',
|
||||
);
|
||||
expect(decideOversizePush({encodedBytes: MAX, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe(
|
||||
'within-budget',
|
||||
);
|
||||
});
|
||||
|
||||
it('drops the first over-budget payload', () => {
|
||||
expect(decideOversizePush({encodedBytes: 1001, maxEncodedBytes: MAX, lastPreparedBytes: null})).toBe('drop');
|
||||
});
|
||||
|
||||
it('drops an over-budget payload that grows', () => {
|
||||
expect(decideOversizePush({encodedBytes: 1200, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe('drop');
|
||||
});
|
||||
|
||||
it('drops an over-budget payload that stays the same size', () => {
|
||||
expect(decideOversizePush({encodedBytes: 1100, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe('drop');
|
||||
});
|
||||
|
||||
it('pushes an over-budget payload that shrinks so removals persist', () => {
|
||||
expect(decideOversizePush({encodedBytes: 1050, maxEncodedBytes: MAX, lastPreparedBytes: 1100})).toBe(
|
||||
'push-shrinks',
|
||||
);
|
||||
});
|
||||
|
||||
it('lets a run of removals drain back under the budget', () => {
|
||||
let last: number | null = null;
|
||||
const decisions: Array<string> = [];
|
||||
for (const bytes of [1300, 1200, 1100, 900]) {
|
||||
decisions.push(decideOversizePush({encodedBytes: bytes, maxEncodedBytes: MAX, lastPreparedBytes: last}));
|
||||
last = bytes;
|
||||
}
|
||||
expect(decisions).toEqual(['drop', 'push-shrinks', 'push-shrinks', 'within-budget']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export const DEFAULT_SYNCED_FIELD_MAX_ENCODED_BYTES = 65_536;
|
||||
|
||||
export const FAVORITE_GIF_MAX_ENCODED_BYTES = 384 * 1024;
|
||||
|
||||
export type OversizePushDecision = 'within-budget' | 'push-shrinks' | 'drop';
|
||||
|
||||
export function decideOversizePush(args: {
|
||||
encodedBytes: number;
|
||||
maxEncodedBytes: number;
|
||||
lastPreparedBytes: number | null;
|
||||
}): OversizePushDecision {
|
||||
if (args.encodedBytes <= args.maxEncodedBytes) return 'within-budget';
|
||||
if (args.lastPreparedBytes != null && args.encodedBytes < args.lastPreparedBytes) return 'push-shrinks';
|
||||
return 'drop';
|
||||
}
|
||||
Reference in new issue
Block a user