feat(core): new worker workspace engine (#9257)

This commit is contained in:
EYHN
2025-01-17 00:22:18 +08:00
committed by GitHub
parent 7dc470e7ea
commit a2ffdb4047
219 changed files with 4267 additions and 7194 deletions

View File

@@ -18,8 +18,11 @@ export class AwarenessSyncImpl implements AwarenessSync {
async update(record: AwarenessRecord, origin?: string) {
await Promise.all(
[this.storages.local, ...Object.values(this.storages.remotes)].map(peer =>
peer.update(record, origin)
[this.storages.local, ...Object.values(this.storages.remotes)].map(
peer =>
peer.connection.status === 'connected'
? peer.update(record, origin)
: Promise.resolve()
)
);
}

View File

@@ -73,10 +73,14 @@ export class BlobSyncImpl implements BlobSync {
async fullSync(signal?: AbortSignal) {
throwIfAborted(signal);
await this.storages.local.connection.waitForConnected(signal);
for (const [remotePeer, remote] of Object.entries(this.storages.remotes)) {
let localList: string[] = [];
let remoteList: string[] = [];
await remote.connection.waitForConnected(signal);
try {
localList = (await this.storages.local.list(signal)).map(b => b.key);
throwIfAborted(signal);
@@ -150,7 +154,7 @@ export class BlobSyncImpl implements BlobSync {
}
stop() {
this.abort?.abort();
this.abort?.abort(MANUALLY_STOP);
this.abort = null;
}

View File

@@ -1,5 +1,5 @@
import type { Observable } from 'rxjs';
import { combineLatest, map, of } from 'rxjs';
import { combineLatest, map, of, ReplaySubject, share } from 'rxjs';
import type { DocStorage, SyncStorage } from '../../storage';
import { DummyDocStorage } from '../../storage/dummy/doc';
@@ -38,18 +38,32 @@ export class DocSyncImpl implements DocSync {
);
private abort: AbortController | null = null;
get state$() {
return combineLatest(this.peers.map(peer => peer.peerState$)).pipe(
map(allPeers => ({
total: allPeers.reduce((acc, peer) => Math.max(acc, peer.total), 0),
syncing: allPeers.reduce((acc, peer) => Math.max(acc, peer.syncing), 0),
synced: allPeers.every(peer => peer.synced),
retrying: allPeers.some(peer => peer.retrying),
errorMessage:
allPeers.find(peer => peer.errorMessage)?.errorMessage ?? null,
}))
) as Observable<DocSyncState>;
}
state$ = combineLatest(this.peers.map(peer => peer.peerState$)).pipe(
map(allPeers =>
allPeers.length === 0
? {
total: 0,
syncing: 0,
synced: true,
retrying: false,
errorMessage: null,
}
: {
total: allPeers.reduce((acc, peer) => Math.max(acc, peer.total), 0),
syncing: allPeers.reduce(
(acc, peer) => Math.max(acc, peer.syncing),
0
),
synced: allPeers.every(peer => peer.synced),
retrying: allPeers.some(peer => peer.retrying),
errorMessage:
allPeers.find(peer => peer.errorMessage)?.errorMessage ?? null,
}
),
share({
connector: () => new ReplaySubject(1),
})
) as Observable<DocSyncState>;
constructor(
readonly storages: PeerStorageOptions<DocStorage>,
@@ -105,7 +119,7 @@ export class DocSyncImpl implements DocSync {
}
stop() {
this.abort?.abort();
this.abort?.abort(MANUALLY_STOP);
this.abort = null;
}

View File

@@ -1,6 +1,6 @@
import { remove } from 'lodash-es';
import { nanoid } from 'nanoid';
import { Observable, Subject } from 'rxjs';
import { Observable, ReplaySubject, share, Subject } from 'rxjs';
import { diffUpdate, encodeStateVectorFromUpdate, mergeUpdates } from 'yjs';
import type { DocStorage, SyncStorage } from '../../storage';
@@ -119,54 +119,65 @@ export class DocSyncPeer {
};
private readonly statusUpdatedSubject$ = new Subject<string | true>();
get peerState$() {
return new Observable<PeerState>(subscribe => {
const next = () => {
if (this.status.skipped) {
subscribe.next({
total: 0,
syncing: 0,
synced: true,
retrying: false,
errorMessage: null,
});
} else if (!this.status.syncing) {
// if syncing = false, jobMap is empty
subscribe.next({
total: this.status.docs.size,
syncing: this.status.docs.size,
synced: false,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
} else {
const syncing = this.status.jobMap.size;
subscribe.next({
total: this.status.docs.size,
syncing: syncing,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
synced: syncing === 0,
});
}
};
peerState$ = new Observable<PeerState>(subscribe => {
const next = () => {
if (this.status.skipped) {
subscribe.next({
total: 0,
syncing: 0,
synced: true,
retrying: false,
errorMessage: null,
});
} else if (!this.status.syncing) {
// if syncing = false, jobMap is empty
subscribe.next({
total: this.status.docs.size,
syncing: this.status.docs.size,
synced: false,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
} else {
const syncing = this.status.jobMap.size;
subscribe.next({
total: this.status.docs.size,
syncing: syncing,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
synced: syncing === 0,
});
}
};
next();
const dispose = this.statusUpdatedSubject$.subscribe(() => {
next();
return this.statusUpdatedSubject$.subscribe(() => {
next();
});
});
}
return () => {
dispose.unsubscribe();
};
}).pipe(
share({
connector: () => new ReplaySubject(1),
})
);
docState$(docId: string) {
return new Observable<PeerDocState>(subscribe => {
const next = () => {
const syncing =
!this.status.connectedDocs.has(docId) ||
this.status.jobMap.has(docId);
if (this.status.skipped) {
subscribe.next({
syncing: false,
synced: true,
retrying: false,
errorMessage: null,
});
}
subscribe.next({
syncing: syncing,
synced: !syncing,
syncing:
!this.status.connectedDocs.has(docId) ||
this.status.jobMap.has(docId),
synced: !this.status.jobMap.has(docId),
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
@@ -524,10 +535,6 @@ export class DocSyncPeer {
const disposes: (() => void)[] = [];
try {
console.info('Remote sync started');
this.status.syncing = true;
this.statusUpdatedSubject$.next(true);
// wait for all storages to connect, timeout after 30s
await Promise.race([
Promise.all([
@@ -547,6 +554,10 @@ export class DocSyncPeer {
}),
]);
console.info('Remote sync started');
this.status.syncing = true;
this.statusUpdatedSubject$.next(true);
// throw error if failed to connect
for (const storage of [this.remote, this.local, this.syncMetadata]) {
// abort if disconnected