-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add push message handler registration and make all pubsub use it. #2735
base: v5
Are you sure you want to change the base?
Changes from 3 commits
9b1ac4c
3340d49
775e3a9
330e266
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import { SinglyLinkedList, DoublyLinkedNode, DoublyLinkedList } from './linked-list'; | ||
import encodeCommand from '../RESP/encoder'; | ||
import { Decoder, PUSH_TYPE_MAPPING, RESP_TYPES } from '../RESP/decoder'; | ||
import { CommandArguments, TypeMapping, ReplyUnion, RespVersions } from '../RESP/types'; | ||
import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub'; | ||
import { TypeMapping, ReplyUnion, RespVersions, CommandArguments } from '../RESP/types'; | ||
import { COMMANDS, ChannelListeners, PUBSUB_TYPE, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub'; | ||
import { AbortError, ErrorReply } from '../errors'; | ||
import { MonitorCallback } from '.'; | ||
|
||
|
@@ -42,6 +42,8 @@ const RESP2_PUSH_TYPE_MAPPING = { | |
[RESP_TYPES.SIMPLE_STRING]: Buffer | ||
}; | ||
|
||
export const pushHandlerError = 'Cannot override built in push message handler'; | ||
|
||
export default class RedisCommandsQueue { | ||
readonly #respVersion; | ||
readonly #maxLength; | ||
|
@@ -51,6 +53,8 @@ export default class RedisCommandsQueue { | |
#chainInExecution: symbol | undefined; | ||
readonly decoder; | ||
readonly #pubSub = new PubSub(); | ||
readonly #pushHandlers: Map<string, (pushMsg: Array<any>) => unknown> = new Map(); | ||
readonly #builtInSet: ReadonlySet<string>; | ||
|
||
get isPubSubActive() { | ||
return this.#pubSub.isActive; | ||
|
@@ -64,6 +68,23 @@ export default class RedisCommandsQueue { | |
this.#respVersion = respVersion; | ||
this.#maxLength = maxLength; | ||
this.#onShardedChannelMoved = onShardedChannelMoved; | ||
|
||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.CHANNELS].message.toString(), this.#pubSub.handleMessageReplyChannel.bind(this.#pubSub)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.CHANNELS].subscribe.toString(), this.#handleStatusReply.bind(this)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.CHANNELS].unsubscribe.toString(), this.#handleStatusReply.bind(this)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.PATTERNS].message.toString(), this.#pubSub.handleMessageReplyPattern.bind(this.#pubSub)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.PATTERNS].subscribe.toString(), this.#handleStatusReply.bind(this)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.PATTERNS].unsubscribe.toString(), this.#handleStatusReply.bind(this)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.SHARDED].message.toString(), this.#pubSub.handleMessageReplySharded.bind(this.#pubSub)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.SHARDED].subscribe.toString(), this.#handleStatusReply.bind(this)); | ||
this.#pushHandlers.set(COMMANDS[PUBSUB_TYPE.SHARDED].unsubscribe.toString(), this.#handleShardedUnsubscribe.bind(this)); | ||
|
||
const s = new Set<string>(); | ||
this.#builtInSet = s; | ||
for (const str of this.#pushHandlers.keys()) { | ||
s.add(str); | ||
} | ||
|
||
this.decoder = this.#initiateDecoder(); | ||
} | ||
|
||
|
@@ -75,28 +96,52 @@ export default class RedisCommandsQueue { | |
this.#waitingForReply.shift()!.reject(err); | ||
} | ||
|
||
#onPush(push: Array<any>) { | ||
// TODO: type | ||
if (this.#pubSub.handleMessageReply(push)) return true; | ||
|
||
const isShardedUnsubscribe = PubSub.isShardedUnsubscribe(push); | ||
if (isShardedUnsubscribe && !this.#waitingForReply.length) { | ||
#handleStatusReply(push: Array<any>) { | ||
const head = this.#waitingForReply.head!.value; | ||
if ( | ||
(Number.isNaN(head.channelsCounter!) && push[2] === 0) || | ||
--head.channelsCounter! === 0 | ||
) { | ||
this.#waitingForReply.shift()!.resolve(); | ||
} | ||
} | ||
|
||
#handleShardedUnsubscribe(push: Array<any>) { | ||
if (!this.#waitingForReply.length) { | ||
const channel = push[1].toString(); | ||
this.#onShardedChannelMoved( | ||
channel, | ||
this.#pubSub.removeShardedListeners(channel) | ||
); | ||
return true; | ||
} else if (isShardedUnsubscribe || PubSub.isStatusReply(push)) { | ||
const head = this.#waitingForReply.head!.value; | ||
if ( | ||
(Number.isNaN(head.channelsCounter!) && push[2] === 0) || | ||
--head.channelsCounter! === 0 | ||
) { | ||
this.#waitingForReply.shift()!.resolve(); | ||
} | ||
} else { | ||
this.#handleStatusReply(push); | ||
} | ||
} | ||
|
||
addPushHandler(messageType: string, handler: (pushMsg: Array<any>) => unknown) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either rename to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm. It's an interesting Q (and then how would remove work?) (and would fit with allowing it to exist with current handlers). Let me think. |
||
if (this.#builtInSet.has(messageType)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to block it? if the user wants a "global" listener - why not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is javascript, dont want to let people hang themselves. |
||
throw new Error(pushHandlerError); | ||
} | ||
|
||
this.#pushHandlers.set(messageType, handler); | ||
} | ||
|
||
removePushHandler(messageType: string) { | ||
if (this.#builtInSet.has(messageType)) { | ||
throw new Error(pushHandlerError); | ||
} | ||
|
||
this.#pushHandlers.delete(messageType); | ||
} | ||
|
||
#onPush(push: Array<any>) { | ||
const handler = this.#pushHandlers.get(push[0].toString()); | ||
if (handler) { | ||
handler(push); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
#getTypeMapping() { | ||
|
@@ -108,9 +153,7 @@ export default class RedisCommandsQueue { | |
onReply: reply => this.#onReply(reply), | ||
onErrorReply: err => this.#onErrorReply(err), | ||
onPush: push => { | ||
if (!this.#onPush(push)) { | ||
|
||
} | ||
return this.#onPush(push); | ||
}, | ||
getTypeMapping: () => this.#getTypeMapping() | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we want to have
builtInSet
or just "hard-code" it instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the set object would remain, much quicker to lookup in a set than multiple if conditions. not that this is very commonly used code path (i.e. just at registration/unregistration time)