use per-content promise map for concurrent decryption

This commit is contained in:
austinkelsay 2025-05-10 17:00:10 -05:00
parent c54785353e
commit f0f5b54768
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE

View File

@ -4,7 +4,8 @@ import axios from 'axios';
export const useDecryptContent = () => { export const useDecryptContent = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const inProgressRef = useRef(false); // Map of in-progress decryption promises, keyed by content hash
const inProgressMap = useRef(new Map());
const cachedResults = useRef({}); const cachedResults = useRef({});
const decryptContent = async (encryptedContent) => { const decryptContent = async (encryptedContent) => {
@ -13,49 +14,63 @@ export const useDecryptContent = () => {
return null; return null;
} }
// Prevent multiple simultaneous calls // Use first 20 chars as our cache/lock key
if (inProgressRef.current) { const cacheKey = encryptedContent.substring(0, 20);
// Wait for a small delay to prevent tight loop
await new Promise(resolve => setTimeout(resolve, 100));
// Return a cached result if we have one
const firstChars = encryptedContent.substring(0, 20);
if (cachedResults.current[firstChars]) {
return cachedResults.current[firstChars];
}
return null;
}
// Check if we've already decrypted this content // Check if we've already decrypted this content
const firstChars = encryptedContent.substring(0, 20); if (cachedResults.current[cacheKey]) {
if (cachedResults.current[firstChars]) { return cachedResults.current[cacheKey];
return cachedResults.current[firstChars];
} }
try { // Check if this specific content is already being decrypted
inProgressRef.current = true; if (inProgressMap.current.has(cacheKey)) {
setIsLoading(true); // Return the existing promise for this content
setError(null); try {
return await inProgressMap.current.get(cacheKey);
const response = await axios.post('/api/decrypt', { encryptedContent }); } catch (error) {
// If the existing promise rejects, we'll try again below
if (response.status !== 200) { console.warn('Previous decryption attempt failed, retrying');
throw new Error(`Failed to decrypt: ${response.statusText}`);
} }
}
const decryptedContent = response.data.decryptedContent;
// Create a new decryption promise for this content
// Cache the result const decryptPromise = (async () => {
cachedResults.current[firstChars] = decryptedContent; try {
setIsLoading(true);
return decryptedContent; setError(null);
const response = await axios.post('/api/decrypt', { encryptedContent });
if (response.status !== 200) {
throw new Error(`Failed to decrypt: ${response.statusText}`);
}
const decryptedContent = response.data.decryptedContent;
// Cache the successful result
cachedResults.current[cacheKey] = decryptedContent;
return decryptedContent;
} catch (error) {
setError(error.message || 'Decryption failed');
// Re-throw to signal failure to awaiter
throw error;
} finally {
setIsLoading(false);
// Remove this promise from the in-progress map
inProgressMap.current.delete(cacheKey);
}
})();
// Store the promise in our map
inProgressMap.current.set(cacheKey, decryptPromise);
// Return the promise
try {
return await decryptPromise;
} catch (error) { } catch (error) {
setError(error.message || 'Decryption failed'); // We've already set the error state in the promise
return null; return null;
} finally {
setIsLoading(false);
inProgressRef.current = false;
} }
}; };