Files
wu-huiandGitHub 72c31adb5d Memory LRU GC (#6943)
Add LRU GC for memory cache.
2023-04-18 14:32:45 -04:00

293 lines
9.5 KiB
TypeScript

/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TimerId } from '../../../src/util/async_queue';
import { doc, query } from '../../util/helpers';
import { describeSpec, specTest } from './describe_spec';
import { client, spec } from './spec_builder';
describeSpec('Persistence:', [], () => {
specTest(
'Local mutations are persisted and re-sent',
['durable-persistence'],
() => {
return spec()
.userSets('collection/key1', { foo: 'bar' })
.userSets('collection/key2', { baz: 'quu' })
.restart()
.expectNumOutstandingWrites(2)
.writeAcks('collection/key1', 1, { expectUserCallback: false })
.writeAcks('collection/key2', 2, { expectUserCallback: false })
.expectNumOutstandingWrites(0);
}
);
specTest(
'Persisted local mutations are visible to listeners',
['durable-persistence'],
() => {
const query1 = query('collection');
return (
spec()
.userSets('collection/key1', { foo: 'bar' })
.userSets('collection/key2', { baz: 'quu' })
.restart()
// It should be visible to listens.
.userListens(query1)
.expectEvents(query1, {
added: [
doc('collection/key1', 0, { foo: 'bar' }).setHasLocalMutations(),
doc('collection/key2', 0, { baz: 'quu' }).setHasLocalMutations()
],
fromCache: true,
hasPendingWrites: true
})
);
}
);
specTest('Remote documents are persisted', ['durable-persistence'], () => {
const query1 = query('collection');
const doc1 = doc('collection/key', 1000, { foo: 'bar' });
return spec()
.userListens(query1)
.watchAcksFull(query1, 1000, doc1)
.expectEvents(query1, { added: [doc1] })
.restart()
.userListens(query1, { resumeToken: 'resume-token-1000' })
.expectEvents(query1, { added: [doc1], fromCache: true });
});
specTest("Remote documents from watch are not GC'd", [], () => {
const query1 = query('collection');
const doc1 = doc('collection/key', 1000, { foo: 'bar' });
return (
spec()
.ensureManualLruGC()
.userListens(query1)
.watchAcksFull(query1, 1000, doc1)
.expectEvents(query1, { added: [doc1] })
// Normally this would clear the cached remote documents.
.userUnlistens(query1)
.userListens(query1, { resumeToken: 'resume-token-1000' })
.expectEvents(query1, { added: [doc1], fromCache: true })
);
});
specTest("Remote documents from user sets are not GC'd", [], () => {
const query1 = query('collection');
return (
spec()
.ensureManualLruGC()
.userSets('collection/key', { foo: 'bar' })
// Normally the write would get GC'd from remote documents here.
.writeAcks('collection/key', 1000)
.userListens(query1)
// Version is 0 since we never received a server version via watch.
.expectEvents(query1, {
added: [
doc('collection/key', 1000, {
foo: 'bar'
}).setHasCommittedMutations()
],
fromCache: true
})
);
});
specTest('Mutation Queue is persisted across uid switches', [], () => {
return spec()
.userSets('users/anon', { uid: 'anon' })
.changeUser('user1')
.expectNumOutstandingWrites(0)
.userSets('users/user1', { uid: 'user1' })
.userSets('users/user1', { uid: 'user1', extra: true })
.changeUser(null)
.expectNumOutstandingWrites(1)
.writeAcks('users/anon', 1000)
.changeUser('user1')
.expectNumOutstandingWrites(2)
.writeAcks('users/user1', 2000)
.writeAcks('users/user1', 3000);
});
specTest(
'Mutation Queue is persisted across uid switches (with restarts)',
['durable-persistence'],
() => {
return spec()
.userSets('users/anon', { uid: 'anon' })
.changeUser('user1')
.expectNumOutstandingWrites(0)
.userSets('users/user1', { uid: 'user1' })
.userSets('users/user1', { uid: 'user1', extra: true })
.changeUser(null)
.restart()
.expectNumOutstandingWrites(1)
.writeAcks('users/anon', 1000, { expectUserCallback: false })
.changeUser('user1')
.restart()
.expectNumOutstandingWrites(2)
.writeAcks('users/user1', 2000, { expectUserCallback: false })
.writeAcks('users/user2', 3000, { expectUserCallback: false });
}
);
specTest('Visible mutations reflect uid switches', [], () => {
const query1 = query('users');
const existingDoc = doc('users/existing', 0, { uid: 'existing' });
const anonDoc = doc('users/anon', 0, {
uid: 'anon'
}).setHasLocalMutations();
const user1Doc = doc('users/user1', 0, {
uid: 'user1'
}).setHasLocalMutations();
return (
spec()
.userListens(query1)
.watchAcksFull(query1, 500, existingDoc)
.expectEvents(query1, { added: [existingDoc] })
.userSets('users/anon', { uid: 'anon' })
.expectEvents(query1, { added: [anonDoc], hasPendingWrites: true })
.changeUser('user1')
// A user change will re-send the query with the current resume token
.expectActiveTargets({ query: query1, resumeToken: 'resume-token-500' })
.expectEvents(query1, { removed: [anonDoc] })
.userSets('users/user1', { uid: 'user1' })
.expectEvents(query1, { added: [user1Doc], hasPendingWrites: true })
.changeUser(null)
.expectEvents(query1, {
removed: [user1Doc],
added: [anonDoc],
hasPendingWrites: true
})
);
});
specTest('Detects all active clients', ['multi-client'], () => {
return (
client(0)
// While we don't verify the client's visibility in this test, the spec
// test framework requires an explicit action before setting an
// expectation.
.becomeHidden()
.expectNumActiveClients(1)
.client(1)
.becomeVisible()
.expectNumActiveClients(2)
);
});
specTest('Single tab acquires primary lease', ['multi-client'], () => {
// This test simulates primary state handoff between two background tabs.
// With all instances in the background, the first active tab acquires
// ownership.
return client(0)
.becomeHidden()
.expectPrimaryState(true)
.client(1)
.becomeHidden()
.expectPrimaryState(false)
.client(0)
.shutdown()
.client(1)
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(true);
});
specTest('Foreground tab acquires primary lease', ['multi-client'], () => {
// This test verifies that in a multi-client scenario, a foreground tab
// takes precedence when a new primary client is elected.
return (
client(0)
.becomeHidden()
.expectPrimaryState(true)
.client(1)
.becomeHidden()
.expectPrimaryState(false)
.client(2)
.becomeVisible()
.expectPrimaryState(false)
.client(0)
// Shutdown the client that is currently holding the primary lease.
.shutdown()
.client(1)
// Client 1 is in the background and doesn't grab the primary lease as
// client 2 is in the foreground.
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(false)
.client(2)
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(true)
);
});
specTest('Primary lease bound to network state', ['multi-client'], () => {
return (
client(0)
// If there is only a single tab, the online state is ignored and the
// tab is always primary
.expectPrimaryState(true)
.disableNetwork()
.expectPrimaryState(true)
.client(1)
.expectPrimaryState(false)
.client(0)
// If the primary tab is offline, and another tab becomes active, the
// primary tab releases its primary lease.
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(false)
.client(1)
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(true)
.disableNetwork()
// If all tabs are offline, the primary lease is retained.
.expectPrimaryState(true)
.client(0)
.enableNetwork()
.expectPrimaryState(false)
.client(1)
.runTimer(TimerId.ClientMetadataRefresh)
// The offline primary tab releases its lease since another tab is now
// online.
.expectPrimaryState(false)
.client(0)
.runTimer(TimerId.ClientMetadataRefresh)
.expectPrimaryState(true)
);
});
specTest(
'clearPersistence() shuts down other clients',
['multi-client'],
() => {
return client(0)
.becomeVisible()
.client(1)
.client(2)
.client(0)
.shutdown()
.clearPersistence()
.client(1)
.expectIsShutdown()
.client(2)
.expectIsShutdown();
}
);
});