def _lockResource(lockName, lockval, relockKey): # TODO 处理重入锁定 lockkey = 'lock:' + lockName + ':' + str(lockval) ftlock = _FTLOCKS.get(lockkey, None) if ftlock == None: ftlock = FTLock(lockkey, relockKey) _FTLOCKS[lockkey] = ftlock ftlog.debug('lock resource of', lockkey, 'wait !!', relockKey) ftlock.lock(relockKey) ftlog.debug('lock resource of', lockkey, 'locked !!', relockKey) return ftlock
def __init__(self, room): self.room = room self.locker = FTLock(self.__class__.__name__ + "_%d" % id(self)) self._state = self.STATE_IDLE # 玩家从队列分配到牌桌过程中,不允许玩家leave room,以免造成开局缺人问题。 # 初始化玩家和牌桌对列 self.users = OrderedDict() self.sittingUsers = set() # 正在坐下的玩家,不允许离开 self.activeTableIds = set() self._initIdleTableIds()
def _findOrCreateLocker(self, key): """ 发现或者创建锁 :param key: :return: """ locker = self._lockMap.get(key) if not locker: locker = FTLock('%s_%s' % (id(self), key)) self._lockMap[key] = locker return locker
def getTcTemplates(moduleKey, funDecode): ''' 取得配置系统的一个游戏相关的键值的json对象值(list或dict类型) ''' # 若有缓存,直接返回缓存内容, 其缓存内容为funAdjust的返回值,而非原始数据 datas = _templatesCache.get(moduleKey, None) if datas: return datas else: locker = _templatesLockers.get(moduleKey, None) if not locker: locker = FTLock(moduleKey) _templatesLockers[moduleKey] = locker with lock(locker): return _getTcTemplates(moduleKey, funDecode)
def __init__(self, room, tableId, dealer): super(TableBase, self).__init__() # 房间 self._room = room # 当前状态 self._state = dealer.sm.findStateByName('idle') # 桌子ID self._tableId = tableId # 玩法 self._dealer = dealer # 桌子定时器 self._timer = None # 所有座位 self._seats = [] # 锁 self.locker = FTLock(self.__class__.__name__ + "_%d" % id(self))
def handlerCommand(msg): """ TCP消息命令处理总入口 """ orgcmd = msg.getCmd() if orgcmd == _runenv._CMD_RPC_: return rpccore._handlerRpcCommand(msg) orgaction = msg.getParam('action') gameId = msg.getParam('gameId') cmd, action = None, None try: if orgaction == None: orgaction = '' # ftlog.debug('handlerCommand->cmd, action->', cmd, action) cmdpath, cmd, action = oldcmd.convertCmdPath(orgcmd, orgaction, msg) # ftlog.debug('handlerCommand->cmd, action, cmdpath, ver, gameId ->', cmd, action, cmdpath, ver, gameId) # 先以cmd#action#gameId去查找命令处理器 vercalls = None if gameId: vercalls = _runenv._cmd_path_methods.get(cmdpath + '#' + str(gameId), None) # 再以cmd#action去查找命令处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get(cmdpath, None) # 再以cmd#*去查找名利处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get(str(cmd) + '#*#' + str(gameId), None) # 再以cmd#*去查找名利处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get(str(cmd) + '#*', None) # ftlog.debug('handlerCommand->vercalls->', vercalls) # 再经过clietnId的版本号过滤 markParams = None if vercalls: if len(vercalls) > 1: ver = getClientIdVer(msg) for vercall in vercalls: if ver >= vercall[0]: markParams = vercall[1] break else: markParams = vercalls[0][1] else: vercalls = [] # 若未找到对应的mark定义, 错误返回 if not markParams: raise Exception('the cmd path method not found ! cmdpath=' + str(cmdpath) + ' clientIdVer=' + str(getClientIdVer(msg)) + ' vercalls len=' + str(len(vercalls)) + ' gameId=' + str(gameId) + ' msg=' + str(msg)) # 锁定当前mark定义的资源 lockParamName = markParams.get('lockParamName', None) if lockParamName: lockval = msg.getParam(lockParamName) if lockParamName == 'userId' or lockParamName == 'tableId': assert (isinstance(lockval, int)) lockkey = 'lock:' + lockParamName + ':' + str(lockval) ftlock = _runenv._FTLOCKS.get(lockkey, None) if ftlock == None: ftlock = FTLock(lockkey) _runenv._FTLOCKS[lockkey] = ftlock ftlog.debug('lock resource of', lockkey, 'wait !!') ftlock.lock() ftlog.debug('lock resource of', lockkey, 'locked !!') try: if markParams['isRpc']: # RPC CALL _handlerRpcCommand(markParams, msg) else: # CLIENT MSG CALL _handlerMsgCommand(markParams, msg) finally: # 释放当前mark定义的资源 if lockParamName: ftlog.debug('lock resource of', lockkey, 'released !!') if ftlock.unlock() == 0: del _runenv._FTLOCKS[lockkey] pass except Exception, e: ftlog.error('cmd=' + str(cmd) + ' action=' + str(action) + ' gameId=' + str(gameId), 'orgcmdpath=' + str(orgcmd) + '#' + str(orgaction)) if router.isQuery(): targs = ftsvr.getTaskRunArg() if not targs.get('responsed') and targs.get('userheader1') == 'CQ': response = newErrorMsgPack(1, str(e)) router.responseQurery(response)
def handlerCommand(msg): """ TCP消息命令处理总入口 """ orgcmd = msg.getCmd() if orgcmd == _runenv._CMD_RPC_: return rpccore._handlerRpcCommand(msg) orgaction = msg.getParam('action') gameId = msg.getParam('gameId') cmd, action = None, None try: if orgaction == None: orgaction = '' # ftlog.debug('handlerCommand->cmd, action->', cmd, action) cmdpath, cmd, action = oldcmd.convertCmdPath(orgcmd, orgaction, msg) # ftlog.debug('handlerCommand->cmd, action, cmdpath, ver, gameId ->', cmd, action, cmdpath, ver, gameId) # 先以cmd#action#gameId去查找命令处理器 vercalls = None if gameId: vercalls = _runenv._cmd_path_methods.get( cmdpath + '#' + str(gameId), None) # 再以cmd#action去查找命令处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get(cmdpath, None) # 再以cmd#*去查找名利处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get( str(cmd) + '#*#' + str(gameId), None) # 再以cmd#*去查找名利处理器 if not vercalls: vercalls = _runenv._cmd_path_methods.get(str(cmd) + '#*', None) # ftlog.debug('handlerCommand->vercalls->', vercalls) # 再经过clietnId的版本号过滤 markParams = None if vercalls: if len(vercalls) > 1: ver = getClientIdVer(msg) for vercall in vercalls: if ver >= vercall[0]: markParams = vercall[1] break else: markParams = vercalls[0][1] else: vercalls = [] # 若未找到对应的mark定义, 错误返回 if not markParams: raise Exception('the cmd path method not found ! cmdpath=' + str(cmdpath) + ' clientIdVer=' + str(getClientIdVer(msg)) + ' vercalls len=' + str(len(vercalls)) + ' gameId=' + str(gameId) + ' msg=' + str(msg)) # 锁定当前mark定义的资源 lockParamName = markParams.get('lockParamName', None) if lockParamName: lockval = msg.getParam(lockParamName) if lockParamName == 'userId' or lockParamName == 'tableId': assert (isinstance(lockval, int)) lockkey = 'lock:' + lockParamName + ':' + str(lockval) ftlock = _runenv._FTLOCKS.get(lockkey, None) if ftlock == None: ftlock = FTLock(lockkey) _runenv._FTLOCKS[lockkey] = ftlock ftlog.debug('lock resource of', lockkey, 'wait !!') ftlock.lock() ftlog.debug('lock resource of', lockkey, 'locked !!') try: if markParams['isRpc']: # RPC CALL _handlerRpcCommand(markParams, msg) else: # CLIENT MSG CALL _handlerMsgCommand(markParams, msg) finally: # 释放当前mark定义的资源 if lockParamName: ftlog.debug('lock resource of', lockkey, 'released !!') if ftlock.unlock() == 0: del _runenv._FTLOCKS[lockkey] pass except Exception, e: ftlog.error( 'cmd=' + str(cmd) + ' action=' + str(action) + ' gameId=' + str(gameId), 'orgcmdpath=' + str(orgcmd) + '#' + str(orgaction)) if router.isQuery(): targs = ftsvr.getTaskRunArg() if not targs.get('responsed') and targs.get('userheader1') == 'CQ': response = newErrorMsgPack(1, str(e)) router.responseQurery(response)
def __init__(self): self.locker = FTLock(self.__class__.__name__ + '_%d' % id(self))
class LedUtils2(object): locker = FTLock("LedUtils") @classmethod def _mkRichTextBody(cls, content): return [{'color': color, 'text': text} for color, text in content] @classmethod def _mkRichTextLedBody(cls, content, _type='led', roomId=0, tableId=0): """ _type: led 纯LED消息,带颜色 watch LED消息,带颜色,观战按钮 vip LED消息,带颜色,进入(坐下)按钮 mtt """ richText = { 'text': cls._mkRichTextBody(content), 'type': _type, 'roomId': roomId, 'tableId': tableId } return RICH_TEXT_LED_MSG_HEADER + json.dumps({'richText': richText}) @classmethod def sendToAllCoServer(cls, mo): ''' 发送消息到所有TCP管理服务器''' msgpack = mo.pack() coSrvIds = gdata.serverTypeMap()[gdata.SRV_TYPE_CONN] for coSrvId in coSrvIds: wrapper.send(coSrvId, msgpack, "S1", "") @classmethod def sendRealTimeLed(cls, gameId, plain_text, receivers=[]): """ 发送一条实时LED消息 receivers: 接收者USERID列表。如果为[],发给所有在线玩家 """ content = [['FFFFFF', plain_text]] cls.sendRealTimeColorfulLed(gameId, content, receivers) @classmethod def sendRealTimeColorfulLed(cls, gameId, content, receivers=[]): """ 发送一条实时LED消息 receivers: 接收者USERID列表。如果为[],发给所有在线玩家 """ mo = MsgPack() mo.setCmd('game_public_led') mo.setParam('gameId', gameId) mo.setParam('receivers', receivers) mo.setParam('msg', cls._mkRichTextLedBody(content)) mo.setParam('ledWithTodoTask', {'text': cls._mkRichTextBody(content)}) cls.sendToAllCoServer(mo) @classmethod def sendled(cls, gameId, plain_text): """ 发送一条实时GDSS那样的LED消息。玩家会在心跳协议中陆续收到该消息""" mo = MsgPack() mo.setCmd('send_led') mo.setParam('msg', plain_text) mo.setParam('gameId', gameId) mo.setParam('ismgr', 1) router.sendUtilServer(mo) @classmethod @locked def _doGamePublicLed(cls, mi): ''' 这个方法在CO进程中由game_public_led的消息handler进行调用, 不允许在其它进程中调用 ''' assert (gdata.serverType() == gdata.SRV_TYPE_CONN) ftlog.debug("<< |msgPackIn:", mi, caller=cls) # 如果从 result 中获取不到数据,从 param 中获取,还取不到,用默认值 gameId = mi.getResult('gameId', 0) or mi.getParam('gameId') or 0 receivers = mi.getResult('receivers') or mi.getParam('receivers') or [] excludeUsers = mi.getResult('excludeUsers') or mi.getParam( 'excludeUsers') or set() msg = mi.getResult('msg') or mi.getParam('msg') timelimit = mi.getResult('timelimit') or mi.getResult( 'timelimit') or {} force = mi.getResult('force') or mi.getParam('force') or [ ] # 这里指定的用户不能过滤,必须收 intervals = timelimit.get('timeLimitIntervals') # 这个时间内收过led的不再收 limitName = timelimit.get('timeLimitName') # 时间间隔的种类,反射机制设置到 user 对象中去 msg = cls._tryParseRichTextLed(msg) leds = [[0, gameId, msg]] ftlog.debug("|leds:", leds, caller=cls) popWinInfo = mi.getResult('popWin') ledWithTodoTask = mi.getResult('ledWithTodoTask') or mi.getParam( 'ledWithTodoTask') # if not ledWithTodoTask: # ledWithTodoTask = Message._richText2Todotask(mi, msg) #TODO: if gameId <= 0: ftlog.error( "doGamePublicLed: error: game not found or gameid is 0", gameId) return mo_cache = {} # 缓存不同版本的 led消息,避免每次生成,浪费CPU def mo_cache_key(gameId, clientVer, clientId): if gameId == 8 and clientVer == 3.37 and 'hall8' in clientId: return gameId, clientVer, 'hall8' return gameId, clientVer def make_led_mo(newleds, clientVer, clientId): mo = MsgPack() mo.setCmd('led') if clientVer >= 3.6: if ledWithTodoTask: mo.setKey('result', ledWithTodoTask) return mo.pack() return None if newleds['origLeds']: mo.setKey('result', newleds['origLeds']) if newleds['richLeds']: mo.setKey('result', newleds['richLeds']) mo.setKey('richText', newleds['richLeds']) mo.setResult('gameId', gameId) if gameId == 8: if clientVer < 3.0: # 德州老单包有问题,所有格式都转为没有按钮的LED (也就是type='led') newleds['richLeds']['type'] = 'led' elif newleds['richLeds'][ 'type'] == 'vip' and clientVer == 3.37 and 'hall8' in clientId: # 德州大厅3.37版本BUG: 如果消息中有 richText,则收不到消息 mo.rmKey('richText') if popWinInfo and clientId.startswith('Winpc'): mo.setResult('popWin', popWinInfo) return mo.pack() def sendled(): newleds = cls._getLedMsgLists(leds, gameId, userId) clientVer = sessiondata.getClientIdVer(userId) clientId = sessiondata.getClientId(userId) if newleds['richLeds'] or newleds['origLeds']: key = mo_cache_key(gameId, clientVer, clientId) if key not in mo_cache: mo_cache[key] = make_led_mo(newleds, clientVer, clientId) mo = mo_cache[key] if mo: protocols.sendCarryMessage(mo, [userId]) if receivers: receivers = receivers + force else: receivers = protocols._ONLINE_USERS.keys() #需求:两次同类型led之间需要相隔intervals now = time.time() sc = 0 for userId in receivers: sc += 1 if sc % 20 == 0: FTTasklet.getCurrentFTTasklet().sleepNb(0.1) if userId in force: if limitName: # setattr(user, limitName, now) userdata.setAttr(userId, limitName, now) sendled() continue if userId not in excludeUsers: if limitName and intervals: # 时间限制 # lastSendTime = getattr(user, limitName, 0) lastSendTime = userdata.getAttr(userId, limitName) if not lastSendTime: lastSendTime = 0 ftlog.debug('|userId, limitName, intervals, lastSendTime:', userId, limitName, intervals, lastSendTime, caller=cls) if lastSendTime + intervals <= now: userdata.setAttr(userId, limitName, now) sendled() else: sendled() @classmethod def _getLedMsgLists(cls, leds, gameId, userId): if not leds: return newleds = { 'richLeds': [], # 带格式的 led 消息 'origLeds': [], # 纯文本的 led 消息 } clientVer = sessiondata.getClientIdVer(userId) # 过滤 led 消息。过滤器把通过过滤器的消息加入到 richLeds 或者 origLeds 里 for led in leds: if cls._ledMsgFilterRich(gameId, userId, clientVer, led, newleds): continue if cls._ledMsgFilterOrig(gameId, userId, clientVer, led, newleds): continue return newleds @classmethod def _ledMsgFilterOrig(cls, gameId, userId, clientVer, led, newleds): """ 这是以前的过滤方法 """ _id, _gid, ledmsg = led if isinstance(ledmsg, str) and ledmsg[0] == '#': # cutVer = TyContext.Configure.get_game_item_float(gameId, 'version.2.2', 2.15) cutVer = 2.15 # TODO: if clientVer and clientVer < cutVer: ledmsg = ledmsg[7:] newleds['origLeds'].append([_id, _gid, ledmsg]) return True @classmethod def _ledMsgFilterRich(cls, gameId, userId, clientVer, led, newleds): """ 过滤富文本 led 消息 """ _id, _gid, ledmsg = led isRichTextLed = isinstance(ledmsg, dict) and ledmsg.has_key('richText') if not isRichTextLed: # 德州1.67版本LED有bug,这是bug处理,3.37解决bug,不对3.37以上的版本做处理,但是3.37的插件又存在这个bug if gameId == 8: # 可以接收富文本格式的LED消息的客户端最低版本 versionCanReceiveRichTextLed = 1.67 versionHaveLedBug = 1.67 versionLedBugFixed = 3.37 clientId = sessiondata.getClientId(userId) if versionLedBugFixed > clientVer >= versionHaveLedBug or 'hall8' not in clientId: ledmsg = { "richText": { "text": [{ "color": "FFFFFF", "text": ledmsg }], "type": "led", "gameId": 8 }, "excludeUsers": set() } ftlog.debug("convert orig led to richtext led", ledmsg) else: return False else: return False # 加入 exclude 机制:业务需求要求一部分LED实时发送给用户,其余用户 # 保留现有心跳时接收 LED 方式。为了让收过LED的用户不再重复接收, # 凡是在 excludeUsers 中的用户就不再接收 LED 了。 if userId in ledmsg['excludeUsers']: return False if gameId == 8: # 可以接收富文本格式的LED消息的客户端最低版本 versionCanReceiveRichTextLed = 1.67 versionHaveLedBug = 1.67 versionLedBugFixed = 3.37 # 德州 1.67 版本加入富文本消息 if clientVer < versionCanReceiveRichTextLed: newleds['origLeds'].append([_id, _gid, ledmsg['plainText']]) # 1.67版本LED有bug,这是bug处理 elif clientVer >= versionHaveLedBug: newleds['richLeds'] = ledmsg['richText'] newleds['origLeds'] = ledmsg['richText'] else: newleds['richLeds'].append(ledmsg['richText']) return True elif gameId == 1: newleds['richLeds'] = ledmsg['richText'] newleds['origLeds'] = ledmsg['richText'] return True return False @classmethod def _tryParseRichTextLed(cls, msg): """检查led消息是否富文本格式。 如果是,解析格式并返回;如否,原样返回;如果出错,返回None msg json 格式示例: { 'richText': { 'text': [{ "color": "RRGGBB", "text": "aaa" },{ "color": "RRGGBB", "text": "bbbccc" }], }, 'excludeUsers': [123456, 32134534] } """ header = 'richTextLedMsg' if not msg.startswith(header): return msg try: ledmsg = json.loads(msg[len(header):]) ledmsg['excludeUsers'] = set(ledmsg.get('excludeUsers', [])) ledmsg['plainText'] = u''.join( (unicode(seg['text']) for seg in ledmsg['richText']['text'])) return ledmsg except: ftlog.error("load led json error", msg) @classmethod @catchedmethod def _richText2Todotask(cls, gamePublicLedMsg, ledMsg): '''richTextLed 消息转换为 game_enter todotask 格式消息 因为 decodeMsgV2 用的 todotask 不是 enter_game,且不知道有没有游戏已经用了那个,所以才写了这个 在实时LED里用(conn.py: doGamePublicLed) ''' text = ledMsg.get('richText', {}).get('text', []) gameId = gamePublicLedMsg.getResult('gameId') roomId = ledMsg.get('roomId') tableId = ledMsg.get('tableId') ledType = ledMsg.get('type') gameMark = { 1: 't3card', 3: 'chess', 6: 'ddz', 7: 'majiang', 8: 'texas', 10: 'douniu' }.get(gameId) lbl = {'vip': u'进入', 'watch': u'观战'}.get(ledType) if not (text and gameId and roomId and tableId and ledType and gameMark and lbl): return gameType = ledMsg.get('gameType', 1) todotaskParams = { 'gameId': gameId, 'roomId': roomId, 'tableId': tableId, 'type': ledType } tasks = [ TodoTaskEnterGame(gameMark, gameType, **todotaskParams).toDict() ] ledresult = {'text': text, 'lbl': lbl, 'tasks': tasks} return ledresult