/**
* Copyright 2024 Beijing Baolande Software Corporation
*
* 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.
*
* Runtime Library Exception to the Apache 2.0 License:
*
* As an exception, if you use this Software to compile your source code and
* portions of this Software are embedded into the binary product as a result,
* you may redistribute such product without providing attribution as would
* otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
*/
package redis_sdk.client
/**
* 处理订阅模式请求发送和响应消息推送
*
* 由于订阅模式不是一命令一响应的模式,不能使用和处理常规命令相同的IoFilter实现:
* 连接未切换成订阅模式时,采用和普通命令的一命令一响应的处理方式,收到响应消息后设置给对应的RedisCommand;
* 连接切换为订阅模式后,不再向RedisCommand设置响应消息,将响应消息放入响应消息队列中,后续由SubscriberEventLoop取走处理;
*/
public class RedisSubscriberHandler <: SingularMessageIoFilter {
private static let instance = RedisSubscriberHandler()
public static func getInstance(): RedisSubscriberHandler {
return instance
}
// 每个连接一个命令队列
private let commandsQueueMap = ConcurrentHashMap<Connection, LinkedBlockingQueue<RedisCommand>>()
// 每个连接一个响应队列, 用于处理订阅模式的命令
private let responseQueueMap = ConcurrentHashMap<Connection, LinkedBlockingQueue<SubscribeResponse>>()
public init() {
// 出现异常时不能跳过此IoFilter
super(skipOnException: false)
}
/**
* 处理入栈消息
*
* @throws TransportException
*/
public func processInboundMessage(context: IoFilterContext, session: Session, inMessage: Any): Unit {
if (let Some(responseQueue) <- getResponseQueue(session.connection)) {
// 订阅模式
let subscribeResponse = SubscribeResponse()
if (let Some(exception) <- context.getException()) {
subscribeResponse.exception = exception
responseQueue.add(subscribeResponse)
return
}
if (let Some(redisMessage) <- (inMessage as RedisMessage)) {
// 订阅模式时,不能忽略Pushes
subscribeResponse.redisMessage = redisMessage
responseQueue.add(subscribeResponse)
return
}
let exception = TransportException("Unsupported message, only accept RedisMessage")
subscribeResponse.exception = exception
responseQueue.add(subscribeResponse)
} else {
let commandsQueue = getCommandsQueue(session.connection)
let command = commandsQueue.remove()
if (let Some(exception) <- context.getException()) {
command.setException(exception)
return
}
if (let Some(redisMessage) <- (inMessage as RedisMessage)) {
if (let Some(redisMessage) <- (redisMessage as PushesRedisMessage)) {
// 非订阅模式忽略Pushes
return
}
command.setResponse(redisMessage)
return
}
let exception = TransportException("Unsupported message, only accept RedisMessage")
command.setException(exception)
}
}
/**
* 处理入栈异常
*
*/
public open func processInboundException(context: IoFilterContext, session: Session, ex: Exception): Unit {
if (let Some(responseQueue) <- getResponseQueue(session.connection)) {
// 订阅模式
let subscribeResponse = SubscribeResponse()
subscribeResponse.exception = ex
responseQueue.add(subscribeResponse)
} else {
let commandsQueue = getCommandsQueue(session.connection)
while (let Some(command) <- commandsQueue.tryRemove()) {
// 主动关闭连接导致读数据异常的场景,命令队列中不一定存在RedisCommand
command.setException(ex)
}
}
}
/**
* 处理出栈消息
*
* @throws TransportException
*/
public func processOutboundMessage(context: IoFilterContext, session: Session, outMessage: Any): Unit {
if (context.isExceptionCaughted()) {
return
}
if (let Some(redisCommand) <- (outMessage as RedisCommand)) {
context.offerMessage(redisCommand)
if (getResponseQueue(session.connection).isNone()) {
// 非订阅模式,入栈命令
let commandsQueue = getCommandsQueue(session.connection)
commandsQueue.add(redisCommand)
}
return
}
let exception = TransportException("Unsupported message, only accept RedisCommand")
context.exceptionCaught(exception)
}
/**
* 处理出栈异常
*
*/
public func processOutboundException(context: IoFilterContext, session: Session, ex: Exception): Unit {
context.exceptionCaught(ex)
}
/**
* 将连接的请求处理切换到订阅模式
*
*/
public func switchToSubscriberMode(session: Session): Unit {
let conn = session.connection
var responseQueue = responseQueueMap.get(conn)
if (let Some(responseQueue) <- responseQueue) {
// 该连接的响应处理已经转换为订阅模式
return
}
let craetedResponseQueue = LinkedBlockingQueue<SubscribeResponse>()
responseQueue = responseQueueMap.addIfAbsent(conn, craetedResponseQueue)
if (responseQueue.isNone()) {
let removeListener = RomoveResponseQueueLister(conn, this)
conn.addListener(removeListener)
// 将读取超时时间设置为无穷大
conn.setTimeoutInfinite()
}
}
/**
* 获取连接对应的响应队列
*/
private func getResponseQueue(conn: Connection): ?LinkedBlockingQueue<SubscribeResponse> {
let responseQueue = responseQueueMap.get(conn)
if (let Some(responseQueue) <- responseQueue) {
return responseQueue
}
return None
}
/**
* 从连接的响应队列中弹出一个响应
*/
public func popSubscribeResponse(session: Session, waitTime: Duration): ?SubscribeResponse {
try {
if (let Some(responseQueue) <- getResponseQueue(session.connection)) {
return responseQueue.remove(waitTime)
}
throw RedisException("Not in subscribe mode")
} catch (ex: Exception) {
if (let Some(redisEx) <- (ex as RedisException)) {
throw redisEx
}
let redisEx = RedisException(ex.message, ex)
throw redisEx
}
}
public func removeResponseQueue(conn: Connection) {
responseQueueMap.remove(conn)
// 还原读取超时时间
conn.rollbackTimeout()
}
/**
* 获取连接对应的命令队列
*/
private func getCommandsQueue(conn: Connection): LinkedBlockingQueue<RedisCommand> {
var commandsQueue = commandsQueueMap.get(conn)
if (let Some(commandsQueue) <- commandsQueue) {
return commandsQueue
}
let createdCommandsQueue = LinkedBlockingQueue<RedisCommand>()
commandsQueue = commandsQueueMap.addIfAbsent(conn, createdCommandsQueue)
if (let Some(commandsQueue) <- commandsQueue) {
return commandsQueue
} else {
if (!conn.isClosed()) {
let removeListener = RomoveResponseQueueLister(conn, this)
conn.addListener(removeListener)
}
return createdCommandsQueue
}
}
public func removeCommandsQueue(conn: Connection) {
commandsQueueMap.remove(conn)
}
public func toString(): String {
return "RedisSubscriberHandler"
}
}
public class SubscribeResponse {
private var redisMessageVal: ?RedisMessage = None
private var exceptionVal: ?Exception = None
public mut prop redisMessage: ?RedisMessage {
get() {
return redisMessageVal
}
set(val) {
this.redisMessageVal = val
}
}
public mut prop exception: ?Exception {
get() {
return exceptionVal
}
set(val) {
this.exceptionVal = val
}
}
}
class RomoveResponseQueueLister <: ConnectionLister {
private let conn: Connection
private let redisPubsubHandler: RedisSubscriberHandler
public init(conn: Connection, redisPubsubHandler: RedisSubscriberHandler) {
this.conn = conn
this.redisPubsubHandler = redisPubsubHandler
}
public func onClose(): Unit {
spawn {
=>
// 延迟一会儿再清理
sleep(Duration.millisecond * 500)
redisPubsubHandler.removeResponseQueue(conn)
redisPubsubHandler.removeCommandsQueue(conn)
}
}
}