2023-07-17 14:07:35 -05:00
|
|
|
import { wait } from '@atproto/common'
|
2024-03-18 22:10:58 +01:00
|
|
|
import { randomStr } from '@atproto/crypto'
|
2023-07-17 14:07:35 -05:00
|
|
|
import {
|
|
|
|
ConsecutiveList,
|
|
|
|
LatestQueue,
|
|
|
|
PartitionedQueue,
|
Feature: Appview v2 (#1924)
* add buf & connectrpc, codegen client
* lint
* prettier ignore
* fix prettier ignore
* tidy & add tests
* filler commit
* rm filler
* server boilerplate
* follows impl
* posts impl
* posts & likes impl
* repost impl
* profiles & handle null values
* list impl
* mutes impl
* blocks impl
* misc
* feed gen impl
* label impl
* notifs impl
* feeds impl
* threads impl
* early sketchwork
* wip
* stub out thick client
* in-progress work on hydrator
* tweak
* hydrate profile labels, detail lists
* feedgen hydration
* protobuf tweaks
* more protobuf tweaks
* wip
* snake case
* moar snake case
* tidy actor hydration
* tidy parsing
* type fixes, renaming, comments in hydrator
* hydrate list items and likes
* hydrate notifications
* feed hydration
* graph & label hydration
* more record protobufs
* pluralize
* tweak pbs
* use new methods
* Setup dataplane grpc client/mock server (#1921)
* add buf & connectrpc, codegen client
* lint
* prettier ignore
* fix prettier ignore
* tidy & add tests
* add record getter mocks
* post view hydration
* fix up mock dataplane to match new protos
* missed one
* wire up dataplane in ctx & dev-env
* adding some basic views
* feed hydration, add blocks to post hydration
* pass over notification hydration
* tidy
* merge
* implement getProfile
* hydrate post aggregation and viewer state
* fix
* fix codegen
* get some tests passing!
* add takedowns & some like bugfixing
* all profile tests passing!
* likes test
* follow endpoints using data plane
* reorg follow block rules
* reposts
* post views!
* implement getList w/ dataplane caveat
* adjust dataplane getListMembers to return listitem uris
* implement getListMutes and -Blocks w/ dataplane
* suggestions
* timeline
* misc view fixes
* view fixes for mutes, self-mute/block
* author feed
* feed gen routes
* tidy
* misc block/mute fixes
* list feed & actor likes
* implement getLists, fix some empty cursors
* implement getMutes, empty profile description fix
* implement getBlocks, block application fix
* implement getSuggestedFollowsByActor, needs some fixes
* feed generation
* search routes
* threads
* tidy
* fix some snaps
* fix getSuggestedFollowsByActor
* implement listNotifications
* implement getUnreadCount w/ dataplane
* implement notifications.updateSeen w/ dataplane
* 3rd party blocking tests
* blocked profile viewer
* add search mocks
* refactor getFeed
* createPipeline -> createPipelineNew
* basic replygating functionality on dataplane w/o filtering violating replies
* hack threadgates into dataplane, apply gates
* deterministic thread orders in dataplane
* misc cleanup around dataplane
* upgrade typescript to v5.3
* update typescript linter deps
* sync bsky proto, codegen
* update dataplane, sync with bsky proto updates
* remove indexer, ingester, daemon, moderation services from appview
* convert more bsky internals to dataplane, remove custom feedgens, implement mute/unmuting in mock dataplane
* remove bsky services. db and indexing logic into mock dataplane.
* remove tests not needed by appview v2, misc reorg
* add basic in-mem repo subscription to dataplane mock
* fix dev-env, bsky tests, bsky build
* cull bsky service entrypoint
* add bsky service readme
* build
* tidy
* tidy, fix pds proxy tests
* fix
* fix bsky entrypoint deps
* support http2 grpc client
* build
* fix dataplane bad tls config/default
* support multiple dataplane urls, retry when unavailable
* build
* tidy/fix
* move dataplane mock tests into their own dir
* cover label hydration through timeline test
* bring back labels in appview tests
* remove unused db primary/replica/coordinator from bsky dataplane
* bsky proto add cids to contracts, buf codegen
* sync-up bsky data-plane w/ codegen updates
* start using dataplane interaction endpoints
* add file
* avoid overfetching from dataplane, plumb feed items and cids
* pass refs through for post viewer state
* switch list feeds to use feed item in dataplane
* handle not found err on get-thread dataplane call
* support use of search service rather than dataplane methods
* mark some appview v2 todos
* tidy
* still use dataplane on search endpoints when search service is not configured
* fix pds test
* fix up bsky tests & snaps
* tidy migrations
* fix appview-v2 docker build
* Support label issuer tied to appview v2 (#2033)
support label issuer tied to appview
* Appview v2: handle empty cursor on list notifications (#2017)
handle empty cursor on appview listnotifs
* Update appview v2 to use author feed enum (#2047)
* update bsky protos with author feed enum, misc feed item changes
* support new author feed enums in dataplane
* fix build
* Appview v2: utilize sorted-at field in bsky protos (#2050)
utilize new sorted-at field in bsky protos
* remove all dataplane usage of GetLikeCounts, switch to GetInteractionCounts
* Appview v2, sync w/ changes to protos (#2071)
* sync bsky protos
* sync-up bsky implementation w/ proto changes
* Appview v2 initial implementation for getPopularFeedGenerators (#2072)
add an initial implementation for getPopularFeedGenerators on appview v2
* merge
* fixes
* fix feed tests
* fix bsync mock
* format
* remove unused config
* fix lockfile
* another lockfile fix
* fix duplicate type
* fix dupplicate test
* Appview v2 handling clearly bad cursors (#2092)
* make mock dataplane cursors different from v1 cursors
* fail open on clearly bad appview cursors
* fix pds appview proxy snaps
* Appview v2 no notifs seen behavior (#2096)
* alter behavior for presenting notifications w/ no last-seen time
* fix pds proxy tests
* Appview v2 dataplane retries based on client host (#2098)
choose dataplane client for retries based on host when possible/relevant
* don't apply negated labels
* display suspensions on actor profile in appview v2
* Appview v2 use dataplane for identity lookups (#2095)
* update bsky proto w/ identity methods
* setup identity endpoints on mock dataplane
* move from idresolver to dataplane for identity lookups on appview
* tidy
* Appview v2: apply safe takedown refs to records, actors (#2107)
apply safe takedown refs to records, actors
* Fix timing on appview v2 repo rev header (#2113)
fix timing on appview repo rev
* fix post thread responses
* Appview v2 don't apply 3p self blocks (#2112)
do not apply 3p self-blocks
* Appview v2 search for feed generators (#2118)
* add protos for feedgen search
* support feed search on getPopularFeedGenerators
* Appview v2 config tidy (#2117)
* remove mod and triage roles from appview
* rename cdn and search config
* remove custom feed harness from appview v2
* Appview v2: don't apply missing modlists (#2122)
* dont apply missing mod lists
* update mock dataplane
* Update packages/bsky/src/hydration/hydrator.ts
Co-authored-by: devin ivy <devinivy@gmail.com>
* refactor & document a bit better
* fix up other routes
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* Appview v2 enforce post thread root boundary (#2120)
* enforce post thread root boundary
* test thread root boundary
* Appview v2 fix admin environment variable (#2137)
fix admin env in appview v2
* Remove re-pagination from getSuggestions (#2145)
* remove re-pagination from getSuggestions
* fix test
* Adjust wording for account suspension (#2153)
adjust wording for account suspension
* Appview v2: fix not-found and blocked uris in threads (#2201)
* fix uris of not-found and blocked posts in threads
* update snaps
* :sparkles: Show author feed of takendown author to admins only (#2197)
* fold in cid, auth, tracing, node version changes
* remove dead config from bsky service entrypoint
* build
* remove ozone test codepaths for appview v2
* tidy, docs fix
---------
Co-authored-by: Devin Ivy <devinivy@gmail.com>
Co-authored-by: Foysal Ahamed <foysal@blueskyweb.xyz>
2024-02-27 14:22:55 -06:00
|
|
|
} from '../../../src/data-plane/server/subscription/util'
|
2023-07-17 14:07:35 -05:00
|
|
|
|
|
|
|
describe('subscription utils', () => {
|
|
|
|
describe('ConsecutiveList', () => {
|
|
|
|
it('tracks consecutive complete items.', () => {
|
|
|
|
const consecutive = new ConsecutiveList<number>()
|
|
|
|
// add items
|
|
|
|
const item1 = consecutive.push(1)
|
|
|
|
const item2 = consecutive.push(2)
|
|
|
|
const item3 = consecutive.push(3)
|
|
|
|
expect(item1.isComplete).toEqual(false)
|
|
|
|
expect(item2.isComplete).toEqual(false)
|
|
|
|
expect(item3.isComplete).toEqual(false)
|
|
|
|
// complete items out of order
|
|
|
|
expect(consecutive.list.length).toBe(3)
|
|
|
|
expect(item2.complete()).toEqual([])
|
|
|
|
expect(item2.isComplete).toEqual(true)
|
|
|
|
expect(consecutive.list.length).toBe(3)
|
|
|
|
expect(item1.complete()).toEqual([1, 2])
|
|
|
|
expect(item1.isComplete).toEqual(true)
|
|
|
|
expect(consecutive.list.length).toBe(1)
|
|
|
|
expect(item3.complete()).toEqual([3])
|
|
|
|
expect(consecutive.list.length).toBe(0)
|
|
|
|
expect(item3.isComplete).toEqual(true)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('LatestQueue', () => {
|
|
|
|
it('only performs most recently queued item.', async () => {
|
|
|
|
const latest = new LatestQueue()
|
|
|
|
const complete: number[] = []
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(1)
|
|
|
|
})
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(2)
|
|
|
|
})
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(3)
|
|
|
|
})
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(4)
|
|
|
|
})
|
|
|
|
await latest.queue.onIdle()
|
|
|
|
expect(complete).toEqual([1, 4]) // skip 2, 3
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(5)
|
|
|
|
})
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(6)
|
|
|
|
})
|
|
|
|
await latest.queue.onIdle()
|
|
|
|
expect(complete).toEqual([1, 4, 5, 6])
|
|
|
|
})
|
|
|
|
|
|
|
|
it('stops processing queued messages on destroy.', async () => {
|
|
|
|
const latest = new LatestQueue()
|
|
|
|
const complete: number[] = []
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(1)
|
|
|
|
})
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(2)
|
|
|
|
})
|
|
|
|
const destroyed = latest.destroy()
|
|
|
|
latest.add(async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(3)
|
|
|
|
})
|
|
|
|
await destroyed
|
|
|
|
expect(complete).toEqual([1]) // 2 was cleared, 3 was after destroy
|
|
|
|
// show that waiting on destroyed above was already enough to reflect all complete items
|
|
|
|
await latest.queue.onIdle()
|
|
|
|
expect(complete).toEqual([1])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('PartitionedQueue', () => {
|
|
|
|
it('performs work in parallel across partitions, serial within a partition.', async () => {
|
|
|
|
const partitioned = new PartitionedQueue({ concurrency: Infinity })
|
|
|
|
const complete: number[] = []
|
|
|
|
// partition 1 items start slow but get faster: slow should still complete first.
|
|
|
|
partitioned.add('1', async () => {
|
2023-07-25 18:54:18 -05:00
|
|
|
await wait(30)
|
2023-07-17 14:07:35 -05:00
|
|
|
complete.push(11)
|
|
|
|
})
|
|
|
|
partitioned.add('1', async () => {
|
2023-07-25 18:54:18 -05:00
|
|
|
await wait(20)
|
2023-07-17 14:07:35 -05:00
|
|
|
complete.push(12)
|
|
|
|
})
|
|
|
|
partitioned.add('1', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(13)
|
|
|
|
})
|
|
|
|
expect(partitioned.partitions.size).toEqual(1)
|
|
|
|
// partition 2 items complete quickly except the last, which is slowest of all events.
|
|
|
|
partitioned.add('2', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(21)
|
|
|
|
})
|
|
|
|
partitioned.add('2', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(22)
|
|
|
|
})
|
|
|
|
partitioned.add('2', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(23)
|
|
|
|
})
|
|
|
|
partitioned.add('2', async () => {
|
2023-07-25 18:54:18 -05:00
|
|
|
await wait(60)
|
2023-07-17 14:07:35 -05:00
|
|
|
complete.push(24)
|
|
|
|
})
|
|
|
|
expect(partitioned.partitions.size).toEqual(2)
|
|
|
|
await partitioned.main.onIdle()
|
|
|
|
expect(complete).toEqual([21, 22, 23, 11, 12, 13, 24])
|
|
|
|
expect(partitioned.partitions.size).toEqual(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('limits overall concurrency.', async () => {
|
|
|
|
const partitioned = new PartitionedQueue({ concurrency: 1 })
|
|
|
|
const complete: number[] = []
|
|
|
|
// if concurrency were not constrained, partition 1 would complete all items
|
|
|
|
// before any items from partition 2. since it is constrained, the work is complete in the order added.
|
|
|
|
partitioned.add('1', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(11)
|
|
|
|
})
|
|
|
|
partitioned.add('2', async () => {
|
|
|
|
await wait(10)
|
|
|
|
complete.push(21)
|
|
|
|
})
|
|
|
|
partitioned.add('1', async () => {
|
|
|
|
await wait(1)
|
|
|
|
complete.push(12)
|
|
|
|
})
|
|
|
|
partitioned.add('2', async () => {
|
|
|
|
await wait(10)
|
|
|
|
complete.push(22)
|
|
|
|
})
|
|
|
|
// only partition 1 exists so far due to the concurrency
|
|
|
|
expect(partitioned.partitions.size).toEqual(1)
|
|
|
|
await partitioned.main.onIdle()
|
|
|
|
expect(complete).toEqual([11, 21, 12, 22])
|
|
|
|
expect(partitioned.partitions.size).toEqual(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('settles with many items.', async () => {
|
|
|
|
const partitioned = new PartitionedQueue({ concurrency: 100 })
|
|
|
|
const complete: { partition: string; id: number }[] = []
|
|
|
|
const partitions = new Set<string>()
|
|
|
|
for (let i = 0; i < 500; ++i) {
|
|
|
|
const partition = randomStr(1, 'base16').slice(0, 1)
|
|
|
|
partitions.add(partition)
|
|
|
|
partitioned.add(partition, async () => {
|
|
|
|
await wait((i % 2) * 2)
|
|
|
|
complete.push({ partition, id: i })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
expect(partitioned.partitions.size).toEqual(partitions.size)
|
|
|
|
await partitioned.main.onIdle()
|
|
|
|
expect(complete.length).toEqual(500)
|
|
|
|
for (const partition of partitions) {
|
|
|
|
const ids = complete
|
|
|
|
.filter((item) => item.partition === partition)
|
|
|
|
.map((item) => item.id)
|
|
|
|
expect(ids).toEqual([...ids].sort((a, b) => a - b))
|
|
|
|
}
|
|
|
|
expect(partitioned.partitions.size).toEqual(0)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|