sessions.py (40218B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\script\sys\sessions.py 2 import copy 3 import sys 4 import types 5 from collections import defaultdict 6 import blue 7 import localization 8 import localstorage 9 import log 10 import uthread 11 from carbon.common.script.net import machobase 12 from carbon.common.script.net.machoNetAddress import MachoAddress 13 from carbon.common.script.net.machoNetExceptions import UnMachoDestination 14 from carbon.common.script.sys import service 15 from carbon.common.script.sys.serviceConst import ROLE_ADMIN, ROLE_PLAYER, ROLE_SERVICE 16 from carbon.common.script.util.format import FmtDateEng 17 from eveexceptions import CancelSessionChange, UserError 18 from eveprefs import prefs 19 from eveservices.roles import has_any_role 20 from markers import PopMark, PushMark 21 from stringutil import strx 22 from carbon.common.script.sys.basesession import allConnectedObjects, allObjectConnections, allSessionsBySID, CallTimer, CallTimersEnabled, CloseSession, CreateSession, dyingObjects, EnableCallTimers, FindClientsAndHoles, FindSessions, GetCallTimes, GetNewSid, GetObjectByUUID, GetObjectUUID, GetServiceSession, IsInClientContext, methodCallHistory, ObjectConnection, outstandingCallTimers, ReadContextSessionTypesPrefs, SESSIONCHANGEDELAY, sessionsByAttribute, sessionsBySID 23 24 def ThrottlePerSeconds(max = 1, seconds = 10, message = 'GenericStopSpamming'): 25 26 def Helper(f): 27 28 def Wrapper(*args, **kwargs): 29 if session and has_any_role(session, ROLE_PLAYER): 30 session.Throttle(f.__name__, max, seconds * const.SEC, message) 31 return f(*args, **kwargs) 32 33 return Wrapper 34 35 return Helper 36 37 38 def ThrottlePerMinute(max = 1, message = 'GenericStopSpamming'): 39 40 def Helper(f): 41 42 def Wrapper(*args, **kwargs): 43 if session and has_any_role(session, ROLE_PLAYER): 44 session.Throttle(f.__name__, max, const.MIN, message) 45 return f(*args, **kwargs) 46 47 return Wrapper 48 49 return Helper 50 51 52 def ThrottlePer5Minutes(max = 1, message = 'GenericStopSpamming'): 53 54 def Helper(f): 55 56 def Wrapper(*args, **kwargs): 57 if session and has_any_role(session, ROLE_PLAYER): 58 session.Throttle(f.__name__, max, 5 * const.MIN, message) 59 return f(*args, **kwargs) 60 61 return Wrapper 62 63 return Helper 64 65 66 def ThrottlePerSecond(max = 1, message = 'GenericStopSpamming'): 67 68 def Helper(f): 69 70 def Wrapper(*args, **kwargs): 71 if session and has_any_role(session, ROLE_PLAYER): 72 session.Throttle(f.__name__, max, const.SEC, message) 73 return f(*args, **kwargs) 74 75 return Wrapper 76 77 return Helper 78 79 80 def ThrottlePerDay(max = 1, message = 'GenericStopSpamming'): 81 82 def Helper(f): 83 84 def Wrapper(*args, **kwargs): 85 if session and has_any_role(session, ROLE_PLAYER): 86 session.Throttle(f.__name__, max, const.DAY, message) 87 return f(*args, **kwargs) 88 89 return Wrapper 90 91 return Helper 92 93 94 def SessionKillah(machoNet): 95 DEF_SESS_TIMEOUT = 120 96 DEF_CTXSESS_TIMEOUT = 60 97 DEF_SLEEPTIME = 30 98 sleepTime = prefs.GetValue('sessionKillah.SleepTime', DEF_SLEEPTIME) * 1000 99 while True: 100 try: 101 blue.pyos.synchro.SleepWallclock(sleepTime) 102 killSessions = prefs.GetValue('sessionKillah.Enable', False) 103 sessTimeout = prefs.GetValue('sessionKillah.SessionTimeout', DEF_SESS_TIMEOUT) * 1000 104 ctxSessTimeout = prefs.GetValue('sessionKillah.ContextSessionTimeout', DEF_CTXSESS_TIMEOUT) * 1000 105 sleepTime = prefs.GetValue('sessionKillah.SleepTime', DEF_SLEEPTIME) * 1000 106 ReadContextSessionTypesPrefs() 107 if killSessions: 108 now = blue.os.GetWallclockTime() 109 toRemove = defaultdict(list) 110 for transport in machoNet.transportsByID.itervalues(): 111 112 def AddIfIrrelevant(sess, timeout): 113 if sess.role & ROLE_SERVICE == 0 and not sess.connectedObjects and getattr(sess, 'charid', None): 114 if sess.contextOnly: 115 irrelevant = now - sess.lastRemoteCall >= timeout 116 else: 117 irrelevant = sess.irrelevanceTime is not None and now - sess.irrelevanceTime >= timeout 118 if irrelevant: 119 toRemove[transport].append(sess.sid) 120 121 for sess in transport.sessions.itervalues(): 122 AddIfIrrelevant(sess, sessTimeout) 123 124 for sess in transport.contextSessions.itervalues(): 125 AddIfIrrelevant(sess, ctxSessTimeout) 126 127 myNodeID = machoNet.GetNodeID() 128 for transport, sids in toRemove.iteritems(): 129 try: 130 log.LogInfo('Asking proxy ', transport.nodeID, ' to remove ', len(sids), ' session(s)') 131 proxySessionMgr = machoNet.session.ConnectToRemoteService('sessionMgr', nodeID=transport.nodeID) 132 proxySessionMgr.RemoveSessionsFromServer(myNodeID, sids) 133 except Exception: 134 log.LogException('While removing irrelevant session from serer') 135 sys.exc_clear() 136 137 except Exception: 138 log.LogException('In SessionKillah loop, caught to keep thread alive') 139 sys.exc_clear() 140 141 142 def GetUndeadObjects(): 143 from carbon.common.script.net.moniker import allMonikers 144 while 1: 145 obs = [] 146 for each in allConnectedObjects: 147 obs.append(each) 148 149 break 150 151 while 1: 152 con = [] 153 for each in allObjectConnections: 154 con.append(each) 155 156 break 157 158 while 1: 159 ses = [] 160 for each in allSessionsBySID.itervalues(): 161 ses.append(each) 162 163 break 164 165 while 1: 166 mon = [] 167 for each in allMonikers: 168 mon.append(each) 169 170 break 171 172 zombies = [] 173 bombies = [] 174 for each in con: 175 liveone = 0 176 for other in ses: 177 for yetanother in other.connectedObjects.itervalues(): 178 if each is yetanother[0]: 179 liveone = 1 180 break 181 182 if not liveone: 183 for other in mon: 184 if other.boundObject is each: 185 liveone = 1 186 break 187 188 if not liveone: 189 zombies.append(each) 190 191 for each in obs: 192 liveone = 0 193 for other in con: 194 if other.__object__ is each: 195 liveone = 1 196 for yetanother in zombies: 197 if yetanother is other: 198 liveone = 2 199 200 if liveone: 201 break 202 203 if not liveone: 204 zombies.append(each) 205 elif liveone == 2: 206 bombies.append(each) 207 208 return (zombies, bombies) 209 210 211 def GetAllSessionInfo(): 212 return (allSessionsBySID, sessionsBySID, sessionsByAttribute) 213 214 215 def GetSessionMaps(): 216 return (sessionsBySID, sessionsByAttribute) 217 218 219 def CountSessions(attr, val): 220 cnt = 0 221 for v in val: 222 try: 223 r = [] 224 for sid in sessionsByAttribute[attr].get(v, {}).iterkeys(): 225 if sid in sessionsBySID: 226 cnt += 1 227 else: 228 r.append(sid) 229 230 for each in r: 231 del sessionsByAttribute[attr][v][each] 232 233 except: 234 srv = sm.services['sessionMgr'] 235 srv.LogError('Session map borked') 236 srv.LogError('sessionsByAttribute=', sessionsByAttribute) 237 srv.LogError('sessionsBySID=', sessionsBySID) 238 log.LogTraceback() 239 raise 240 241 return cnt 242 243 244 def FindClients(attr, val): 245 ret = [] 246 for v in val: 247 try: 248 r = [] 249 for sid in sessionsByAttribute[attr].get(v, {}).iterkeys(): 250 if sid in sessionsBySID: 251 s = sessionsBySID[sid] 252 if hasattr(s, 'clientID'): 253 ret.append(s.clientID) 254 else: 255 r.append(sid) 256 257 for each in r: 258 del sessionsByAttribute[attr][v][each] 259 260 except: 261 srv = sm.services['sessionMgr'] 262 srv.LogError('Session map borked') 263 srv.LogError('sessionsByAttribute=', sessionsByAttribute) 264 srv.LogError('sessionsBySID=', sessionsBySID) 265 log.LogTraceback() 266 raise 267 268 return ret 269 270 271 def FindSessionsByAttributes(sessionValueBySessionAttribute): 272 ret = set() 273 for attribute, attributeValue in sessionValueBySessionAttribute.iteritems(): 274 if attribute == 'sid': 275 sess = sessionsBySID.get(attributeValue, None) 276 if sess: 277 ret.add(sess) 278 elif attribute in sessionsByAttribute: 279 for sid in sessionsByAttribute[attribute].get(attributeValue, {}).keys(): 280 sess = sessionsBySID.get(sid, None) 281 if sess is not None and getattr(sess, attribute, None) == attributeValue: 282 ret.add(sess) 283 284 else: 285 for sess in sessionsBySID.itervalues(): 286 if attribute in ('corprole', 'rolesAtAll', 'rolesAtHQ', 'rolesAtBase', 'rolesAtOther'): 287 if attributeValue & getattr(sess, attribute, 0): 288 ret.add(sess) 289 elif getattr(sess, attribute, None) == attributeValue: 290 ret.add(sess) 291 292 return ret 293 294 295 def GetSessions(sid = None): 296 if sid is None: 297 return sessionsBySID.values() 298 else: 299 return sessionsBySID.get(sid, None) 300 301 302 def GetNumberOfCharacterSessions(): 303 return len(sessionsByAttribute.get('charid', [])) 304 305 306 class SessionMgr(service.Service): 307 __guid__ = 'svc.sessionMgr' 308 __displayname__ = 'Session manager' 309 __exportedcalls__ = {'GetSessionStatistics': [ROLE_SERVICE], 310 'CloseUserSessions': [ROLE_SERVICE], 311 'GetProxyNodeFromID': [ROLE_SERVICE], 312 'GetClientIDsFromID': [ROLE_SERVICE], 313 'UpdateSessionAttributes': [ROLE_SERVICE], 314 'ConnectToClientService': [ROLE_SERVICE], 315 'PerformSessionChange': [ROLE_SERVICE], 316 'GetLocalClientIDs': [ROLE_SERVICE], 317 'EndAllGameSessions': [ROLE_ADMIN | ROLE_SERVICE], 318 'PerformHorridSessionAttributeUpdate': [ROLE_SERVICE], 319 'BatchedRemoteCall': [ROLE_SERVICE], 320 'GetSessionDetails': [ROLE_SERVICE], 321 'TerminateClientConnections': [ROLE_SERVICE | ROLE_ADMIN], 322 'RemoveSessionsFromServer': [ROLE_SERVICE], 323 'ResetClientConnection': [ROLE_SERVICE]} 324 __dependencies__ = [] 325 __notifyevents__ = ['ProcessSessionChange', 326 'DoSessionChanging', 327 'DoSimClockRebase', 328 'OnGlobalConfigChanged', 329 'OnSessionChanged'] 330 331 def __init__(self): 332 service.Service.__init__(self) 333 if machobase.mode == 'server': 334 self.__dependencies__ += ['authentication', 'DB2'] 335 self.sessionClientIDCache = {'userid': {}, 336 'charid': {}} 337 self.proxies = {} 338 self.clientIDsByCharIDCache = {} 339 self.sessionChangeShortCircuitReasons = [] 340 self.sessionChangeSubscribers = {} 341 self.additionalAttribsAllowedToUpdate = [] 342 self.additionalStatAttribs = [] 343 self.additionalSessionDetailsAttribs = [] 344 self.sessionStatistics = None 345 self.timeSessionStatsComputed = None 346 if machobase.mode == 'server': 347 uthread.new(SessionKillah, sm.GetService('machoNet')).context = 'sessions::SessionKillah' 348 349 def GetProxySessionManager(self, nodeID): 350 if nodeID not in self.proxies: 351 self.proxies[nodeID] = self.session.ConnectToProxyServerService('sessionMgr', nodeID) 352 return self.proxies[nodeID] 353 354 def GetLocalClientIDs(self): 355 ret = [] 356 for each in GetSessions(): 357 if hasattr(each, 'clientID'): 358 ret.append(each.clientID) 359 360 return ret 361 362 def GetReason(self, oldReason, newReason, timeLeft): 363 return localization.GetByLabel('/Carbon/UI/Sessions/SessionChangeInProgressBase') 364 365 def __RaisePSCIP(self, oldReason, newReason, timeLeft = None): 366 if oldReason is None: 367 oldReason = '' 368 if newReason is None: 369 newReason = '' 370 reason = self.GetReason(oldReason, newReason, timeLeft) 371 self.LogInfo('raising a PerformSessionChangeInProgress user error with reason ', reason) 372 raise UserError('PerformSessionChangeInProgress', {'reason': reason}) 373 374 def NotifySessionChangeSubscribers(self): 375 for callback in self.sessionChangeSubscribers.values(): 376 callback() 377 378 def RegisterNotifyOfSessionChange(self, subscriber, callback): 379 if subscriber not in self.sessionChangeSubscribers: 380 self.sessionChangeSubscribers[subscriber] = callback 381 382 def UnregisterNotifyOfSessionChange(self, subscriber): 383 if subscriber in self.sessionChangeSubscribers: 384 del self.sessionChangeSubscribers[subscriber] 385 386 def PerformSessionLockedOperation(self, *args, **keywords): 387 return self.PerformSessionChange(*args, **keywords) 388 389 def PerformSessionChange(self, sessionChangeReason, func, *args, **keywords): 390 global sessionChangeDelay 391 if 'hostileMutation' in keywords or 'violateSafetyTimer' in keywords or 'wait' in keywords: 392 kw2 = copy.copy(keywords) 393 hostile = keywords.get('hostileMutation', 0) 394 wait = keywords.get('wait', 0) 395 violateSafetyTimer = keywords.get('violateSafetyTimer', 0) 396 if 'violateSafetyTimer' in kw2: 397 del kw2['violateSafetyTimer'] 398 if 'hostileMutation' in kw2: 399 del kw2['hostileMutation'] 400 if 'wait' in kw2: 401 del kw2['wait'] 402 else: 403 hostile = 0 404 violateSafetyTimer = 0 405 kw2 = keywords 406 wait = 0 407 self.LogInfo('Performing a locked session changing operation at', blue.os.GetSimTime(), 'with next allowed at', session.nextSessionChange, '- reason=', sessionChangeReason) 408 if machobase.mode == 'client': 409 sess = session 410 if not violateSafetyTimer and hostile in (0, 1) and sess.charid: 411 if sess.nextSessionChange is not None and sess.nextSessionChange > blue.os.GetSimTime(): 412 if wait > 0: 413 t = 1000 * (2 + (session.nextSessionChange - blue.os.GetSimTime()) / const.SEC) 414 self.LogInfo('PerformSessionChange is sleeping for %s ms' % t) 415 blue.pyos.synchro.SleepWallclock(t) 416 if sess.nextSessionChange is not None and sess.nextSessionChange > blue.os.GetSimTime(): 417 self.LogError("Too frequent session change attempts. You'll just get yourself stuck doing this. Ignoring.") 418 self.LogError('func=', func, ', args=', args, ', keywords=', keywords) 419 if sessionChangeReason in self.sessionChangeShortCircuitReasons: 420 return 421 self.__RaisePSCIP(sess.sessionChangeReason, sessionChangeReason, sess.nextSessionChange - blue.os.GetSimTime()) 422 else: 423 self.LogInfo('Passing session change stuck prevention speedbump. hostile=', hostile) 424 else: 425 raise RuntimeError('Not Yet Implemented') 426 if sess.mutating and hostile in (0, 1): 427 if sessionChangeReason in self.sessionChangeShortCircuitReasons: 428 self.LogInfo('Ignoring session change attempt due to ' + sessionChangeReason + ' overzealousness') 429 return 430 self.__RaisePSCIP(sess.sessionChangeReason, sessionChangeReason) 431 self.NotifySessionChangeSubscribers() 432 try: 433 if hostile not in (2, 4): 434 self.LogInfo('Incrementing the session mutation flag') 435 sess.mutating += 1 436 if sess.mutating == 1: 437 self.LogInfo('Chaining ProcessSessionMutating event') 438 sm.ChainEvent('ProcessSessionMutating', func, args, kw2) 439 sess.sessionChangeReason = sessionChangeReason 440 if hostile == 0: 441 prev = sess.nextSessionChange 442 if not violateSafetyTimer: 443 sess.nextSessionChange = blue.os.GetSimTime() + sessionChangeDelay 444 localNextSessionChange = sess.nextSessionChange 445 self.LogInfo('Pre-op updating next legal session change to ', FmtDateEng(sess.nextSessionChange)) 446 self.LogInfo('Executing the session modification method') 447 try: 448 return func(*args, **kw2) 449 except Exception as e: 450 if localNextSessionChange >= sess.nextSessionChange: 451 sess.nextSessionChange = prev 452 self.LogInfo('post-op exception handler reverting next legal session change to ', FmtDateEng(sess.nextSessionChange)) 453 else: 454 self.LogInfo("post-op exception handler - Someone else has modified nextSessionChange, so DON'T revert it - modified value is ", FmtDateEng(sess.nextSessionChange)) 455 if not isinstance(e, CancelSessionChange): 456 raise 457 458 elif hostile in (1, 3): 459 self.LogInfo('Initiating Remote Mutation (local state change only), args=', args, ', keywords=', kw2) 460 else: 461 self.LogInfo('Finalizing Remote Mutation (local state change only), args=', args, ', keywords=', kw2) 462 finally: 463 self.LogInfo('Post-op updating next legal session change to ', FmtDateEng(sess.nextSessionChange)) 464 if hostile not in (1, 3): 465 self.LogInfo('Decrementing the session mutation flag') 466 sess.mutating -= 1 467 if sess.mutating == 0: 468 self.LogInfo('Scattering OnSessionMutated event') 469 sm.ScatterEvent('OnSessionMutated', func, args, kw2) 470 471 def PerformSelectiveSessionChange(self, reasonAllowedToViolate, reason, allowWait, func, *args, **keywords): 472 violateSafetyTimer = 0 473 if session.nextSessionChange is not None and session.nextSessionChange > blue.os.GetSimTime(): 474 if session.sessionChangeReason.startswith(reasonAllowedToViolate): 475 violateSafetyTimer = 1 476 kw2 = copy.copy(keywords) 477 kw2['violateSafetyTimer'] = violateSafetyTimer 478 if allowWait: 479 kw2['wait'] = 1 480 return self.PerformSessionChange(reason, func, *args, **kw2) 481 482 def GetProxyNodeFromID(self, idtype, theID, refresh = 0): 483 if idtype != 'clientID': 484 clientID = self.GetClientIDsFromID(idtype, theID, refresh)[0] 485 else: 486 clientID = theID 487 return sm.services['machoNet'].GetProxyNodeIDFromClientID(clientID) 488 489 def IsPlayerCharacter(self, charID): 490 raise Exception('stub function not implemented') 491 492 def GetClientIDsFromID(self, idtype, theID, refresh = 0): 493 clientIDs = set() 494 if theID in sessionsByAttribute[idtype]: 495 sids = sessionsByAttribute[idtype][theID] 496 for sid in sids: 497 if sid in sessionsBySID: 498 s = sessionsBySID[sid] 499 if getattr(s, 'clientID', 0): 500 if theID in self.sessionClientIDCache[idtype]: 501 del self.sessionClientIDCache[idtype][theID] 502 clientIDs.add(s.clientID) 503 504 if not refresh and theID in self.sessionClientIDCache[idtype]: 505 return self.sessionClientIDCache[idtype][theID] 506 if not hasattr(self, 'dbzcluster'): 507 self.dbzcluster = self.DB2.GetSchema('zcluster') 508 clientID = None 509 if idtype == 'charid': 510 if self.IsPlayerCharacter(theID): 511 if theID in self.clientIDsByCharIDCache: 512 clientID, lastTime = self.clientIDsByCharIDCache[theID] 513 if blue.os.GetWallclockTime() - lastTime > const.SEC: 514 clientID = None 515 else: 516 clientIDs.add(clientID) 517 if clientID is None: 518 client = self.dbzcluster.Sessions_ByCharacterID(theID) 519 if len(client) and client[0].clientID: 520 clientID = client[0].clientID 521 clientIDs.add(clientID) 522 self.clientIDsByCharIDCache[theID] = (clientID, blue.os.GetWallclockTime()) 523 else: 524 log.LogTraceback('Thou shall only use GetClientIDsFromID for player characters', show_locals=1) 525 clientID = None 526 elif idtype == 'userid': 527 for row in self.dbzcluster.Sessions_ByUserID(theID): 528 if row.clientID: 529 clientIDs.add(row.clientID) 530 531 else: 532 raise RuntimeError('Can only currently characterID to locate a client through the DB') 533 if not clientIDs: 534 raise UnMachoDestination('The dude is not logged on') 535 else: 536 self.sessionClientIDCache[idtype][theID] = list(clientIDs) 537 return self.sessionClientIDCache[idtype][theID] 538 539 def DoSessionChanging(self, *args): 540 pass 541 542 def ProcessSessionChange(self, isRemote, sess, change): 543 if 'userid' in change and change['userid'][0] in self.sessionClientIDCache['userid']: 544 del self.sessionClientIDCache['userid'][change['userid'][0]] 545 if 'charid' in change and change['charid'][0] in self.sessionClientIDCache['charid']: 546 del self.sessionClientIDCache['charid'][change['charid'][0]] 547 if machobase.mode == 'proxy': 548 return -1 549 550 def OnSessionChanged(self, isRemote, sess, change): 551 if 'charid' in change: 552 oldCharacterID, newCharacterID = change['charid'] 553 sm.ScatterEvent('OnCharacterSessionChanged', oldCharacterID, newCharacterID) 554 555 def DoSimClockRebase(self, times): 556 oldSimTime, newSimTime = times 557 try: 558 session.nextSessionChange += newSimTime - oldSimTime 559 except: 560 log.LogException('Exception while trying to rebase the session change timer') 561 562 def OnGlobalConfigChanged(self, config): 563 global sessionChangeDelay 564 if 'sessionChangeDelay' in config: 565 sessionChangeDelay = int(config['sessionChangeDelay']) * const.SEC 566 567 def TypeAndNodeValidationHook(self, idType, id): 568 pass 569 570 def UpdateSessionAttributes(self, idtype, theID, dict): 571 if idtype not in ['charid', 'userid'] + self.additionalAttribsAllowedToUpdate: 572 raise RuntimeError("You shouldn't be calling this, as you obviously don't know what you're doing. This is like one of the most sensitive things in the system, dude.") 573 if machobase.mode == 'proxy' and theID not in sessionsByAttribute[idtype]: 574 raise UnMachoDestination('Wrong proxy or client not connected') 575 if machobase.mode == 'server' and idtype in ('userid', 'charid'): 576 proxyNodeID = None 577 try: 578 proxyNodeID = self.GetProxyNodeFromID(idtype, theID, 1) 579 except UnMachoDestination: 580 sys.exc_clear() 581 582 if proxyNodeID is not None: 583 return self.GetProxySessionManager(proxyNodeID).UpdateSessionAttributes(idtype, theID, dict) 584 sessions = FindSessions(idtype, [theID]) 585 if idtype == 'charid' and 'flagRolesOnly' in dict and sessions and len(sessions) > 0: 586 sessioncorpid = sessions[0].corpid 587 rolecorpid = dict['corpid'] 588 if sessioncorpid != rolecorpid: 589 self.LogError('Character session is wrong!!! Character', theID, 'has session corp', sessioncorpid, 'but should be', rolecorpid, "I'll fix his session but please investigate why this occurred! Update dict:", dict, 'Session:', sessions[0]) 590 if 'flagRolesOnly' in dict: 591 del dict['flagRolesOnly'] 592 self.TypeAndNodeValidationHook(idtype, theID) 593 parallelCalls = [] 594 for each in sessions: 595 if hasattr(each, 'clientID'): 596 parallelCalls.append((self.PerformHorridSessionAttributeUpdate, (each.clientID, dict))) 597 else: 598 each.LogSessionHistory('Updating session information via sessionMgr::UpdateSessionAttributes') 599 each.SetAttributes(dict) 600 each.LogSessionHistory('Updated session information via sessionMgr::UpdateSessionAttributes') 601 602 if len(parallelCalls) > 60: 603 log.LogTraceback('Horrid session change going haywire. Redesign the calling code!') 604 uthread.parallel(parallelCalls) 605 606 def PerformHorridSessionAttributeUpdate(self, clientID, dict): 607 try: 608 if machobase.mode == 'server': 609 proxyNodeID = self.GetProxyNodeFromID('clientID', clientID) 610 return self.GetProxySessionManager(proxyNodeID).PerformHorridSessionAttributeUpdate(clientID, dict) 611 s = sm.services['machoNet'].GetSessionByClientID(clientID) 612 if s: 613 s.LogSessionHistory('Updating session information via sessionMgr::UpdateSessionAttributes') 614 s.SetAttributes(dict) 615 s.LogSessionHistory('Updated session information via sessionMgr::UpdateSessionAttributes') 616 except Exception: 617 log.LogException() 618 sys.exc_clear() 619 620 def GetSessionFromParams(self, idtype, theID): 621 if idtype == 'clientID': 622 s = sm.services['machoNet'].GetSessionByClientID(theID) 623 if s is None: 624 raise UnMachoDestination('Wrong proxy or client not connected, session not found by clientID=%s' % theID) 625 return s 626 if theID not in sessionsByAttribute[idtype]: 627 raise UnMachoDestination('Wrong proxy or client not connected, session not found by %s=%s' % (idtype, theID)) 628 else: 629 sids = sessionsByAttribute[idtype][theID].keys() 630 if not len(sids) == 1: 631 raise UnMachoDestination('Ambiguous idtype/id pair (%s/%s). There are %d sessions that match them.' % (idtype, theID, len(sids))) 632 else: 633 sid = sids[0] 634 if sid not in sessionsBySID: 635 raise UnMachoDestination("The client's session is in an invalid or terminating state") 636 return sessionsBySID[sid] 637 638 def ConnectToClientService(self, svc, idtype, theID): 639 if machobase.mode == 'proxy': 640 s = self.GetSessionFromParams(idtype, theID) 641 return sm.services['machoNet'].ConnectToRemoteService(svc, MachoAddress(clientID=s.clientID, service=svc), s) 642 proxyNodeID = self.GetProxyNodeFromID(idtype, theID) 643 try: 644 return self.GetProxySessionManager(proxyNodeID).ConnectToClientService(svc, idtype, theID) 645 except UnMachoDestination: 646 sys.exc_clear() 647 if not refreshed: 648 return self.GetProxySessionManager(self.GetProxyNodeFromID(idtype, theID, 1)).ConnectToClientService(svc, idtype, theID) 649 650 def GetSessionStatistics(self): 651 now = blue.os.GetWallclockTime() 652 statAttributes = ['userid', 'usertype'] + self.additionalStatAttribs 653 if self.sessionStatistics is None or now - self.timeSessionStatsComputed > 5 * const.SEC: 654 self.timeSessionStatsComputed = now 655 self.sessionStatistics = {} 656 for attribute, valuesOfAttribute in sessionsByAttribute.iteritems(): 657 if attribute in statAttributes: 658 attrValueCounts = {} 659 for attrValue, valueSessions in valuesOfAttribute.iteritems(): 660 attrValueCounts[attrValue] = len(valueSessions) 661 662 self.sessionStatistics[attribute] = [len(valuesOfAttribute), attrValueCounts] 663 664 self._AddToSessionStatistics() 665 return self.sessionStatistics 666 667 def _AddToSessionStatistics(self): 668 machoChar = machoUser = 0 669 for sess in sessionsBySID.itervalues(): 670 if sess.sessionType == const.session.SESSION_TYPE_GAME: 671 if sess.charid: 672 machoChar += 1 673 else: 674 machoUser += 1 675 676 self.sessionStatistics['CARBON:MachoChar'] = (machoChar, {None: machoChar}) 677 self.sessionStatistics['CARBON:MachoUser'] = (machoUser, {None: machoUser}) 678 679 def Run(self, memstream = None): 680 service.Service.Run(self, memstream) 681 self.AppRun(memstream) 682 683 def AppRun(self, memstream = None): 684 pass 685 686 def BatchedRemoteCall(self, batchedCalls): 687 retvals = [] 688 for callID, (service, method, args, keywords) in batchedCalls.iteritems(): 689 try: 690 c = '%s::%s (Batched\\Server)' % (service, method) 691 timer = PushMark(c) 692 try: 693 with CallTimer(c): 694 retvals.append((0, callID, getattr(sm.GetService(service), method)(*args, **keywords))) 695 finally: 696 PopMark(timer) 697 698 except Exception as e: 699 if getattr(e, '__passbyvalue__', 0): 700 retvals.append((1, callID, strx(e))) 701 else: 702 retvals.append((1, callID, e)) 703 sys.exc_clear() 704 705 return retvals 706 707 def GetSessionValuesFromRowset(self, si): 708 return {} 709 710 def GetInitialValuesFromCharID(self, charID): 711 return {} 712 713 def CloseUserSessions(self, userIDs, reason, clientID = None): 714 if type(userIDs) not in (types.ListType, types.TupleType): 715 userIDs = [userIDs] 716 for each in FindSessions('userid', userIDs): 717 if clientID is None or not hasattr(each, 'clientID') or each.clientID != clientID: 718 each.LogSessionHistory(reason) 719 CloseSession(each) 720 721 def ResetClientConnection(self, clientID, sessionID = None): 722 try: 723 if machobase.mode != 'proxy': 724 raise RuntimeError('This cannot run on a %s' % machobase.mode) 725 if clientID is None: 726 raise RuntimeError('Cannot reset client connection None') 727 log.LogNotice('ResetClientConnection', clientID, sessionID) 728 if sessionID is not None: 729 sessionToReset = sessionsBySID.get(sessionID, None) 730 if sessionToReset is None: 731 log.LogError('Failed to find client session with sessionID %s. Cannot reset client connection.' % sessionID) 732 return False 733 else: 734 for sess in sessionsBySID.itervalues(): 735 if getattr(sess, 'clientID', None) == clientID: 736 sessionToReset = sess 737 break 738 else: 739 log.LogError('Failed to find client session with clientID %s. Cannot reset client connection.' % clientID) 740 return False 741 742 sm.ChainEvent('ProcessProxySessionReset', sessionToReset) 743 log.LogNotice('ResetClientConnection: Instructing all sols to remove', sessionToReset) 744 sessionToReset.LogSessionHistory('Removing sessions from sol nodes') 745 self._RemoveSessionFromTransports(sessionToReset) 746 sessionToReset.LogSessionHistory('Resetting session information via sessionMgr::ResetClientConnection') 747 sessionToReset.ClearCharacterDependantAttributes() 748 sessionToReset.LogSessionHistory('Session information reset sessionMgr::ResetClientConnection') 749 except Exception: 750 log.LogException() 751 raise 752 753 return True 754 755 @staticmethod 756 def _RemoveSessionFromTransports(clientSession): 757 sassies = FindSessionsByAttributes({'userid': clientSession.userid, 758 'charid': clientSession.charid, 759 'shipid': clientSession.shipid}) 760 transports = {t for t in sm.GetService('machoNet').transportsByID.itervalues() if t.nodeID is not None} 761 for transport in transports: 762 for sess in sassies: 763 try: 764 transport.RemoveSessionFromServer(sess) 765 except Exception: 766 log.LogException('While removing irrelevant session from server') 767 768 def TerminateClientConnections(self, reason, filter): 769 if machobase.mode != 'proxy' or not isinstance(filter, types.DictType) or len(filter) == 0: 770 raise RuntimeError('TerminateClientConnections should only be called on a proxy and with a non-empty filter dictionnary') 771 numDisconnected = 0 772 for clientSession in GetSessions(): 773 blue.pyos.BeNice() 774 clientID = getattr(clientSession, 'clientID', None) 775 if clientID is None: 776 continue 777 if clientSession.role & ROLE_SERVICE: 778 continue 779 for attr, value in filter.iteritems(): 780 if not (hasattr(clientSession, attr) and getattr(clientSession, attr) == value): 781 break 782 else: 783 numDisconnected += 1 784 clientSession.LogSessionHistory('Connection terminated by administrator %s, reason is: %s' % (str(session.userid), reason)) 785 sm.GetService('machoNet').TerminateClient(reason, clientID) 786 787 return numDisconnected 788 789 def EndAllGameSessions(self, remote = 0): 790 if remote: 791 self.session.ConnectToAllProxyServerServices('sessionMgr').EndAllGameSessions() 792 else: 793 txt = '' 794 for s in GetSessions(): 795 blue.pyos.BeNice() 796 if hasattr(s, 'clientID') and not s.role & ROLE_SERVICE: 797 sid = s.sid 798 s.LogSessionHistory('Session closed by administrator %s' % str(session.userid)) 799 CloseSession(s, True) 800 801 def RemoveSessionsFromServer(self, nodeID, sessionIDs): 802 if machobase.mode != 'proxy': 803 raise RuntimeError('RemoveSessionsFromServer should only be called on a proxy') 804 log.LogInfo('CTXSESS: RemoveSessionsFromServer(nodeID=', nodeID, '), with ', len(sessionIDs), ' session IDs') 805 mn = sm.services['machoNet'] 806 serverTID = mn.transportIDbySolNodeID.get(nodeID, None) 807 if serverTID is not None: 808 serverTransport = mn.transportsByID[serverTID] 809 for sid in sessionIDs: 810 sess = sessionsBySID.get(sid, None) 811 if sess is not None: 812 uthread.worker('SessionMgr::RemoveSesssionsFromServer', serverTransport.RemoveSessionFromServer, sess) 813 814 else: 815 log.LogWarning('RemoveSessionsFromServer() called with unknown or non-server nodeID ', nodeID) 816 817 def GetSessionDetails(self, clientID, sid): 818 from carbon.common.script.util import htmlwriter 819 macho = sm.GetService('machoNet') 820 if clientID: 821 s = macho.GetSessionByClientID(clientID) 822 else: 823 s = GetSessions(sid) 824 if s is None: 825 return 826 info = [['sid', s.sid], 827 ['version', s.version], 828 ['clientID', getattr(s, 'clientID', '')], 829 ['userid', s.userid], 830 ['userType', s.userType], 831 ['role', s.role], 832 ['charid', s.charid], 833 ['lastRemoteCall', s.lastRemoteCall]] 834 for each in self.additionalSessionDetailsAttribs: 835 info.append([each, s.__dict__[each]]) 836 837 sessionsBySID, sessionsByAttribute = GetSessionMaps() 838 for each in info: 839 if each[0] == 'sid': 840 if each[1] not in sessionsBySID: 841 each[1] = str(each[1]) + ' <b>(Not in sessionsBySID)</b>' 842 elif each[0] in sessionsByAttribute: 843 a = getattr(s, each[0]) 844 if a: 845 if a not in sessionsByAttribute[each[0]]: 846 each[1] = str(each[1]) + " <b>(Not in sessionsByAttribute['%s'])</b>" % each[0] 847 elif s.sid not in sessionsByAttribute[each[0]].get(a, {}): 848 each[1] = str(each[1]) + " <b>(Not in sessionsByAttribute['%s']['%s'])</b>" % (each[0], a) 849 850 info.append(['IP Address', getattr(s, 'address', '?')]) 851 connectedObjects = [] 852 hd = ['ObjectID', 'References', 'Object'] 853 for k, v in s.machoObjectsByID.iteritems(): 854 tmp = [k, '%s.%s' % (FmtDateEng(v[0]), v[0] % const.SEC), htmlwriter.Swing(str(v[1]))] 855 if isinstance(v[1], ObjectConnection): 856 tmp[2] = str(tmp[2]) + ' (c2ooid=%s)' % str(v[1].__dict__['__c2ooid__']) 857 if v[1].__dict__['__c2ooid__'] not in s.connectedObjects: 858 tmp[2] = str(tmp[2]) + ' <b>(Not in s.connectedObjects)</b>' 859 else: 860 object = v[1].__dict__['__object__'] 861 if s.sid not in object.sessionConnections: 862 tmp[2] = str(tmp[2]) + ' <b>(s.sid not in object.sessionConnections)</b>' 863 if s.sid not in object.objectConnections or v[1].__dict__['__c2ooid__'] not in object.objectConnections[s.sid]: 864 tmp[2] = str(tmp[2]) + ' <b>([s.sid][c2ooid]) not part of object.objectConnections)</b>' 865 if k not in sessionsByAttribute['objectID']: 866 tmp[2] = str(tmp[2]) + " <b>(Not in sessionsByAttribute['objectID'])</b>" 867 elif s.sid not in sessionsByAttribute['objectID'][k]: 868 tmp[2] = str(tmp[2]) + " <b>(Not in sessionsByAttribute['objectID']['%s'])</b>" % k 869 connectedObjects.append(tmp) 870 871 sessionHistory = [] 872 lastEntry = '' 873 i = 0 874 for each in s.sessionhist: 875 tmp = each[2].replace('\n', '<br>') 876 if tmp == lastEntry: 877 txt = '< same >' 878 else: 879 txt = tmp 880 lastEntry = tmp 881 sessionHistory.append((each[0], 882 i, 883 each[1], 884 htmlwriter.Swing(txt))) 885 i += 1 886 887 streamInfo = [] 888 try: 889 streams = macho.transportsByID[macho.transportIDbySessionID[s.sid]].readers 890 for stream in streams: 891 streamInfo.append((stream.streamID, 892 stream.request.remote_addr, 893 stream.sequence_number, 894 stream.reconnects, 895 stream.GetActiveSetSize())) 896 897 except KeyError: 898 pass 899 900 return (info, 901 connectedObjects, 902 sessionHistory, 903 s.calltimes, 904 s.sessionVariables, 905 streamInfo) 906 907 908 def IsSessionChangeConnect(change): 909 if 'charid' in change and change['charid'][0] is None and change['charid'][1] is not None: 910 return change['charid'][1] 911 return False 912 913 914 def IsSessionChangeDisconnect(change, character = False): 915 key = 'charid' if character else 'userid' 916 if key in change and change[key][0] is not None and change[key][1] is None: 917 return change[key][0] 918 else: 919 return False 920 921 922 def IsSolarSystemChange(change): 923 return 'solarsystemid2' in change 924 925 926 class ClientContext(localstorage.UpdatedLocalStorage): 927 928 def __init__(self): 929 localstorage.UpdatedLocalStorage.__init__(self, {'base.ClientContext': True}) 930 931 932 class MethodCachingContext(localstorage.UpdatedLocalStorage): 933 934 def __init__(self, methodCachingScope): 935 localstorage.UpdatedLocalStorage.__init__(self, {'base.MethodCachingContext': methodCachingScope}) 936 937 938 def CachedMethodCalled(cacheKey, details): 939 try: 940 cacheScope = localstorage.GetLocalStorage()['base.MethodCachingContext'] 941 if details: 942 clientTimes = details['versionCheck'] 943 if clientTimes: 944 clientTime = clientTimes[0] 945 cacheScope[cacheKey] = (sm.GetService('objectCaching').__versionchecktimes__[clientTime], 'sessionInfo' in details) 946 except KeyError: 947 pass 948 949 950 sessionChangeDelay = SESSIONCHANGEDELAY