Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

819 lines
30 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include "sharedobjecttransaction.h"
  8. #include "sqlaccess/sqlaccess.h"
  9. // memdbgon must be the last include file in a .cpp file!!!
  10. #include "tier0/memdbgon.h"
  11. namespace GCSDK
  12. {
  13. //-----------------------------------------------------------------------------
  14. // Purpose:
  15. //-----------------------------------------------------------------------------
  16. class CTrustedHelper_OutputAndSetErrorState
  17. {
  18. public:
  19. CTrustedHelper_OutputAndSetErrorState( CSharedObjectTransactionEx& SharedObjectTransaction, const char *pszFunctionContext, const CSteamID& CacheOwnerSteamID, const CSharedObject *pObject )
  20. : m_SharedObjectTransaction( SharedObjectTransaction )
  21. , m_pszFunctionContext( pszFunctionContext )
  22. , m_CacheOwnerSteamID( CacheOwnerSteamID )
  23. , m_pObject( pObject )
  24. {
  25. }
  26. void operator()( const bool bExpResult, const char *pszExp ) const
  27. {
  28. if ( !bExpResult )
  29. {
  30. AssertMsg4( bExpResult, "Failed verification: %s (context '%s'; owner '%s'; object '%s')", pszExp, m_pszFunctionContext, m_CacheOwnerSteamID.Render(), m_pObject ? m_pObject->GetDebugString().String() : "[none]" );
  31. m_SharedObjectTransaction.SetErrorState();
  32. }
  33. }
  34. private:
  35. CSharedObjectTransactionEx& m_SharedObjectTransaction;
  36. const char *m_pszFunctionContext;
  37. const CSteamID& m_CacheOwnerSteamID;
  38. const CSharedObject *m_pObject;
  39. };
  40. #define CSOTVerifyBase( exp_, obj_ ) \
  41. CVerifyIfTrustedHelper( CTrustedHelper_OutputAndSetErrorState( *this, __FUNCTION__, m_pLockedSOCache->GetOwner(), obj_ ), (exp_), #exp_ ).GetResult()
  42. #define CSOTVerify( exp_ ) \
  43. CSOTVerifyBase( exp_, NULL )
  44. #define CSOTVerifyObj( exp_ ) \
  45. CSOTVerifyBase( exp_, pObject )
  46. //-----------------------------------------------------------------------------
  47. // Purpose:
  48. //-----------------------------------------------------------------------------
  49. CSharedObjectTransactionEx::CSharedObjectTransactionEx( CGCSharedObjectCache *pLockedSOCache, const char *pszTransactionName )
  50. : m_pLockedSOCache( pLockedSOCache )
  51. , m_pSQLAccess( &m_sqlAccessInternal )
  52. {
  53. Assert( pszTransactionName );
  54. Assert( pszTransactionName[0] );
  55. Assert( m_pLockedSOCache );
  56. // We have to start a SQL transaction no matter what. If we don't do this, then
  57. // any code internally or externally that tries to add operations to the open transaction
  58. // will instead run immediately. We Verify() here because we know the only thing that
  59. // can fail beginning a new transaction is to already be in a transaction, and that
  60. // can't be the case because we just made this object.
  61. m_bTransactionBuildSuccess = m_pSQLAccess->BBeginTransaction( pszTransactionName );
  62. Verify( m_bTransactionBuildSuccess );
  63. // Grab another lock on our user that owns the cache we got passed in. This means that,
  64. // barring any maliscious or really terrible code happening on the outside, even if our
  65. // calling code unlocks *their* lock, we'll still have ours and can access the cache safely.
  66. Verify( GGCBase()->BLockSteamIDImmediate( m_pLockedSOCache->GetOwner() ) );
  67. // Because we just constructed a fresh object, we want to guarantee that our internal state
  68. // is consistent. This way, either we're guaranteed to fail construction or we know moving
  69. // forward that we started in a good place so anything that's wrong since then is some kind
  70. // of real programmer error.
  71. Verify( BIsValidInternalState() );
  72. }
  73. //-----------------------------------------------------------------------------
  74. // Purpose:
  75. //-----------------------------------------------------------------------------
  76. CSharedObjectTransactionEx::~CSharedObjectTransactionEx()
  77. {
  78. // If we fall off the stack and we haven't been submitted, manually rollback whatever work
  79. // we queued up in SQL and free up our local storage. This isn't an error.
  80. if ( m_pSQLAccess->BInTransaction() )
  81. {
  82. Rollback();
  83. }
  84. // We're finally done manipulating this user's cache. If we're practicing best
  85. // practices for locking, this will still leave us locked in our outer scope. If we're
  86. // not, at least we'll be safe.
  87. GGCBase()->UnlockSteamID( m_pLockedSOCache->GetOwner() );
  88. }
  89. //-----------------------------------------------------------------------------
  90. // Purpose:
  91. //-----------------------------------------------------------------------------
  92. bool CSharedObjectTransactionEx::BIsValidInternalState()
  93. {
  94. // Sanity check basic internal data.
  95. Assert( m_pSQLAccess );
  96. // If we've done something bad with our transaction (ie., committed from outside, or
  97. // tried to submit through this object) and we're still doing work, that's a case we
  98. // can't handle.
  99. if ( !CSOTVerify( m_pSQLAccess->BInTransaction() ) )
  100. return false;
  101. // Verify we have a cache and it's a cache we understand how to manipulate.
  102. if ( !CSOTVerify( m_pLockedSOCache ) || !CSOTVerify( m_pLockedSOCache->GetOwner().IsValid() ) || !CSOTVerify( m_pLockedSOCache->GetOwner().BIndividualAccount() ) )
  103. return false;
  104. // Transactions can yield when trying to commit, so we need to be running a job. We also want
  105. // to be paranoid and make sure that the lock is actively held by our current job.
  106. AssertRunningJob();
  107. if ( !CSOTVerify( GGCBase()->IsSteamIDLockedByJob( m_pLockedSOCache->GetOwner(), &GJobCur() ) ) )
  108. return false;
  109. // Internal state is such that we can at least try to perform operations, at least.
  110. return true;
  111. }
  112. //-----------------------------------------------------------------------------
  113. // Purpose:
  114. //-----------------------------------------------------------------------------
  115. bool CSharedObjectTransactionEx::BIsValidInput( const CSharedObject *pObject )
  116. {
  117. if ( !BIsValidInternalState() )
  118. return false;
  119. if ( !CSOTVerifyObj( pObject ) )
  120. return false;
  121. // Make sure we have a valid type ID for this object. There aren't any objects that return negative
  122. // IDs, but if we pass in a deleted or bogus pointer, we'll probably crash here when we hit the vtable.
  123. if ( !CSOTVerifyObj( pObject->GetTypeID() > 0 ) )
  124. return false;
  125. return true;
  126. }
  127. //-----------------------------------------------------------------------------
  128. // Purpose:
  129. //-----------------------------------------------------------------------------
  130. bool CSharedObjectTransactionEx::BTrackModifiedObjectInternal( CSharedObject *pObject, CSharedObject **out_ppWritableObject )
  131. {
  132. if ( !BIsValidInput( pObject ) )
  133. return false;
  134. if ( !CSOTVerifyObj( out_ppWritableObject ) )
  135. return false;
  136. // If an object is in the added list, we're going to do a full write so modifying it at this point adds nothing
  137. // new. We'll return the current only version of it as our writable object.
  138. {
  139. const CreateOrDestroyCommitInfo_t *pInfo = InternalFindCommitInfo( pObject, m_vecObjects_Added );
  140. if ( pInfo )
  141. {
  142. *out_ppWritableObject = pInfo->m_pObject;
  143. return true;
  144. }
  145. }
  146. // If an object is in the modified list, we've already made a copy that we can make modifications to, so we'll
  147. // return that copy.
  148. {
  149. const ModifyCommitInfo_t *pInfo = InternalFindCommitInfo( pObject, m_vecObjects_Modified );
  150. if ( pInfo )
  151. {
  152. *out_ppWritableObject = pInfo->m_pWriteableObject;
  153. return true;
  154. }
  155. }
  156. // We aren't already tracking this object. We're acting as if we've never seen it before, so first make sure that
  157. // we're in the cache we think we're in.
  158. if ( !CSOTVerify( m_pLockedSOCache->FindSharedObject( *pObject ) ) )
  159. return false;
  160. // Make a copy of our current state that we can make modifications to and track the association.
  161. *out_ppWritableObject = CSharedObject::Create( pObject->GetTypeID() );
  162. (*out_ppWritableObject)->Copy( *pObject );
  163. m_vecObjects_Modified.AddToTail( ModifyCommitInfo_t( *out_ppWritableObject, pObject ) );
  164. return true;
  165. }
  166. //-----------------------------------------------------------------------------
  167. // Purpose:
  168. //-----------------------------------------------------------------------------
  169. bool CSharedObjectTransactionEx::BAddNewObjectInternal( CSharedObject *pObject )
  170. {
  171. // Make sure we pass basic sanity check measures for our inputs (ie., we have inputs, we have appropriate
  172. // locks to manipulate those inputs, etc.).
  173. if ( !BIsValidInput( pObject ) )
  174. return false;
  175. // Make sure this object isn't already in this cache. This can cause problems during rollback if we didn't
  176. // really add it as part of the transaction and remove it when a transaction fails.
  177. if ( !CSOTVerifyObj( m_pLockedSOCache->FindSharedObject( *pObject ) == NULL ) )
  178. return false;
  179. // Make sure this object isn't already in the list of objects we're adding as part of this transaction.
  180. // Having the object in the list multiple times is potentially harmless, but it probably indicates some
  181. // calling code is doing something we don't expect.
  182. if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Added ) == NULL ) )
  183. return false;
  184. // Success.
  185. m_vecObjects_Added.AddToTail( CreateOrDestroyCommitInfo_t( pObject ) );
  186. return true;
  187. }
  188. //-----------------------------------------------------------------------------
  189. // Purpose:
  190. //-----------------------------------------------------------------------------
  191. bool CSharedObjectTransactionEx::BRemoveObjectInternal( CSharedObject *pObject )
  192. {
  193. // Make sure we pass basic sanity check measures for our inputs (ie., we have inputs, we have appropriate
  194. // locks to manipulate those inputs, etc.).
  195. if ( !BIsValidInput( pObject ) )
  196. return false;
  197. // Make sure the object we're removing is in the cache we're trying to remove it from.
  198. if ( !CSOTVerifyObj( m_pLockedSOCache->FindSharedObject( *pObject ) ) )
  199. return false;
  200. // Look through our lists of objects that we're adding and objects that we're modifying. If we're
  201. // removing an object that we're adding in the same transaction through the same pointer, this will
  202. // result in a broken SO cache. Removing a modified object may or may not be safe but it's probably
  203. // indicative of a higher-level logic bug regardless.
  204. if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Added ) == NULL ) )
  205. return false;
  206. if ( !CSOTVerifyObj( InternalFindCommitInfo( pObject, m_vecObjects_Modified ) == NULL ) )
  207. return false;
  208. // Success.
  209. m_vecObjects_Removed.AddToTail( CreateOrDestroyCommitInfo_t( pObject ) );
  210. return true;
  211. }
  212. //-----------------------------------------------------------------------------
  213. // Purpose:
  214. //-----------------------------------------------------------------------------
  215. const CSharedObject *CSharedObjectTransactionEx::InternalFindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject& soIndex ) const
  216. {
  217. FOR_EACH_VEC( m_vecObjects_Modified, i )
  218. {
  219. auto& info = m_vecObjects_Modified[i];
  220. if ( info.m_pObject->BIsKeyEqual( soIndex ) )
  221. return info.m_pObject;
  222. }
  223. FOR_EACH_VEC( m_vecObjects_Added, i )
  224. {
  225. auto& info = m_vecObjects_Added[i];
  226. if ( info.m_pObject->BIsKeyEqual( soIndex ) )
  227. return info.m_pObject;
  228. }
  229. return NULL;
  230. }
  231. //-----------------------------------------------------------------------------
  232. // Purpose:
  233. //-----------------------------------------------------------------------------
  234. const char *CSharedObjectTransactionEx::InternalPreCommit()
  235. {
  236. // ...
  237. if ( !BIsValidInternalState() )
  238. return "invalid internal state";
  239. // ...
  240. if ( !CSOTVerify( m_pSQLAccess->BInTransaction() ) )
  241. return "transaction closed before commit";
  242. // Did we run into some error internally building this transaction? This doesn't assert because it
  243. // could be a SQL error or something else that doesn't necessarily indicate a problem with the way
  244. // we're using this class. It *probably* indicates the calling code has a problem, but it isn't
  245. // guaranteed so we don't assert.
  246. if ( !m_bTransactionBuildSuccess )
  247. return "error(s) building transaction";
  248. // Nothing wrong so far.
  249. return NULL;
  250. }
  251. //-----------------------------------------------------------------------------
  252. // Purpose:
  253. //-----------------------------------------------------------------------------
  254. bool CSharedObjectTransactionEx::BYieldingCommit()
  255. {
  256. const char *pszPreCommitFailureDesc = InternalPreCommit();
  257. if ( pszPreCommitFailureDesc )
  258. {
  259. // We can't spit out information about which cache we're dealing with here because it's possible at
  260. // this point that the error we're displaying is "we don't have the cache anymore!" which means pulling
  261. // memory is probably unsafe.
  262. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': %s!\n", GetInternalTransactionDesc(), pszPreCommitFailureDesc );
  263. Rollback();
  264. return false;
  265. }
  266. bool bNetworkRelevantObjectsChanged = false;
  267. bool bDBRelevantObjectsChanged = false;
  268. bool bGeneratedCommitSQL = true;
  269. // Add insert statements to SQL access instance for everything that needs to be written to the
  270. // database. Some types only exist in memory and have no database backing.
  271. FOR_EACH_VEC( m_vecObjects_Added, i )
  272. {
  273. CreateOrDestroyCommitInfo_t& info = m_vecObjects_Added[i];
  274. if ( info.m_pObject->BIsDatabaseBacked() )
  275. {
  276. bGeneratedCommitSQL &= info.m_pObject->BYieldingAddInsertToTransaction( *m_pSQLAccess );
  277. bDBRelevantObjectsChanged |= true;
  278. }
  279. bNetworkRelevantObjectsChanged |= info.m_pObject->BIsNetworked();
  280. }
  281. // For every object that we've changed state on, we've also been tracking information on which
  282. // fields we modified. Here we ask each of the modified objects (which aren't currently in the
  283. // SO cache, but are clones living outside of it) to queue up their updates to the SQL transaction.
  284. {
  285. CUtlVector< int > vecDirtyFields;
  286. FOR_EACH_VEC( m_vecObjects_Modified, i )
  287. {
  288. const ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
  289. Assert( info.m_pObject );
  290. Assert( info.m_pWriteableObject );
  291. Assert( info.m_pObject != info.m_pWriteableObject );
  292. if ( info.m_pWriteableObject->BIsDatabaseBacked() )
  293. {
  294. vecDirtyFields.RemoveAll();
  295. m_SODirtyList.GetDirtyFieldSetByObj( info.m_pWriteableObject, vecDirtyFields );
  296. bGeneratedCommitSQL &= info.m_pWriteableObject->BYieldingAddWriteToTransaction( *m_pSQLAccess, vecDirtyFields );
  297. bDBRelevantObjectsChanged |= true;
  298. }
  299. bNetworkRelevantObjectsChanged |= info.m_pWriteableObject->BIsNetworked();
  300. AssertMsg4( info.m_pWriteableObject->BIsDatabaseBacked() == info.m_pObject->BIsDatabaseBacked(),
  301. "Disagreement over DB backing state between SOs '%s' and '%s' in transaction '%s' for user '%s'!",
  302. info.m_pObject->GetDebugString().String(),
  303. info.m_pWriteableObject->GetDebugString().String(),
  304. GetInternalTransactionDesc(),
  305. m_pLockedSOCache->GetOwner().Render() );
  306. AssertMsg4( info.m_pWriteableObject->BIsNetworked() == info.m_pObject->BIsNetworked(),
  307. "Disagreement over network state between SOs '%s' and '%s' in transaction '%s' for user '%s'!",
  308. info.m_pObject->GetDebugString().String(),
  309. info.m_pWriteableObject->GetDebugString().String(),
  310. GetInternalTransactionDesc(),
  311. m_pLockedSOCache->GetOwner().Render() );
  312. }
  313. }
  314. // Have each object that we'd like to remove queue up the SQL work necessary to do so.
  315. FOR_EACH_VEC( m_vecObjects_Removed, i )
  316. {
  317. CreateOrDestroyCommitInfo_t& info = m_vecObjects_Removed[i];
  318. if ( info.m_pObject->BIsDatabaseBacked() )
  319. {
  320. bGeneratedCommitSQL &= info.m_pObject->BYieldingAddRemoveToTransaction( *m_pSQLAccess );
  321. bDBRelevantObjectsChanged |= true;
  322. }
  323. // We don't have to update network state here as removes are sent immediately to the client
  324. // before we've even flushed our dirty updates.
  325. }
  326. // Our "did we generate the SQL to do this work in the DB" variable starts off true, so the only
  327. // way we'd expect it to be false here is if we attempted to do work above and ran into some errors.
  328. // If we don't have any SQL work to do at all, for example if we're only adding/modifying/removing
  329. // memory-only items, then "bGeneratedCommitSQL" will be true and "bDBRelevantObjectsChanged" will
  330. // be false.
  331. if ( !bGeneratedCommitSQL )
  332. {
  333. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s' for user '%s': failed to add inserts/writes.\n", GetInternalTransactionDesc(), m_pLockedSOCache->GetOwner().Render() );
  334. Rollback();
  335. return false;
  336. }
  337. // Try to commit DB transaction. We don't know for sure whether we're the only code that's adding commands
  338. // to this transaction, so we can't completely skip this if we didn't do any SQL work internally. We can
  339. // say "did we do anything internally?; if not, maybe we'll be empty".
  340. if ( !m_pSQLAccess->BCommitTransaction( !bDBRelevantObjectsChanged ) )
  341. {
  342. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s' for user '%s': SQL transaction failure.\n", GetInternalTransactionDesc(), m_pLockedSOCache->GetOwner().Render() );
  343. Rollback();
  344. return false;
  345. }
  346. // The database work committed successfully, so we update our memory state to match the state we just wrote
  347. // to the DB.
  348. FOR_EACH_VEC( m_vecObjects_Added, i )
  349. {
  350. CreateOrDestroyCommitInfo_t& info = m_vecObjects_Added[i];
  351. m_pLockedSOCache->AddObject( info.m_pObject ); // internally will assert if cache isn't locked
  352. }
  353. FOR_EACH_VEC( m_vecObjects_Modified, i )
  354. {
  355. ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
  356. info.m_pObject->Copy( *info.m_pWriteableObject ); // stomp the version that's already in the cache with the properties from where we've been writing
  357. delete info.m_pWriteableObject;
  358. }
  359. FOR_EACH_VEC( m_vecObjects_Removed, i )
  360. {
  361. CreateOrDestroyCommitInfo_t &info = m_vecObjects_Removed[i];
  362. Verify( m_pLockedSOCache->BDestroyObject( *info.m_pObject, false ) ); // internally will assert if cache isn't locked
  363. }
  364. // Did we change anything that affected network state? If so, tell our cache to flush the dirty
  365. // state list.
  366. if ( bNetworkRelevantObjectsChanged )
  367. {
  368. // Our cache is responsible for sending all the network updates, including creates from the
  369. // AddObject() calls above, so now that our commit has succeeded we copy over our list of
  370. // dirty objects from inside this transaction.
  371. for ( const ModifyCommitInfo_t& info : m_vecObjects_Modified )
  372. {
  373. m_pLockedSOCache->DirtyNetworkObject( info.m_pObject );
  374. }
  375. m_pLockedSOCache->SendAllNetworkUpdates();
  376. }
  377. // Cleanup.
  378. m_vecObjects_Added.RemoveAll();
  379. m_vecObjects_Modified.RemoveAll();
  380. m_vecObjects_Removed.RemoveAll();
  381. return true;
  382. }
  383. void CSharedObjectTransactionEx::Rollback()
  384. {
  385. // Clean up any memory allocated to handle any database work that is outstanding but not yet
  386. // committed.
  387. m_pSQLAccess->RollbackTransaction();
  388. Assert( !m_pSQLAccess->BInTransaction() );
  389. // Clean up any in-memory changes that are currently outstanding.
  390. // We made new objects for our adds, so we need to free up that memory.
  391. FOR_EACH_VEC( m_vecObjects_Added, i )
  392. {
  393. delete m_vecObjects_Added[i].m_pObject;
  394. }
  395. m_vecObjects_Added.RemoveAll();
  396. // For our modifies, we haven't done any work on the versions that are in the cache, so all we have
  397. // to do is delete the temp memory that we allocated for a writable version.
  398. FOR_EACH_VEC( m_vecObjects_Modified, i )
  399. {
  400. ModifyCommitInfo_t& info = m_vecObjects_Modified[i];
  401. delete info.m_pWriteableObject;
  402. }
  403. m_vecObjects_Modified.RemoveAll();
  404. // We didn't actually do any in-memory work for our items that were set to be deleted, so we
  405. // can just free the memory for our tracking state.
  406. m_vecObjects_Removed.RemoveAll();
  407. }
  408. CSharedObjectTransaction::CSharedObjectTransaction( CSQLAccess &sqlAccess, const char *pName )
  409. : m_sqlAccess( sqlAccess )
  410. {
  411. if ( m_sqlAccess.BInTransaction() == false )
  412. {
  413. m_sqlAccess.BBeginTransaction( pName );
  414. }
  415. }
  416. CSharedObjectTransaction::~CSharedObjectTransaction()
  417. {
  418. Rollback();
  419. }
  420. CSharedObjectTransaction::undoinfo_t *CSharedObjectTransaction::FindObjectInVector( const CSharedObject *pObject, CUtlVector<undoinfo_t> &vec ) const
  421. {
  422. FOR_EACH_VEC( vec, i )
  423. {
  424. if ( vec[i].pObject == pObject )
  425. return &vec[i];
  426. }
  427. return NULL;
  428. }
  429. bool CSharedObjectTransaction::AssertValidInput( const CGCSharedObjectCache *pSOCache, const CSharedObject *pObject, const char *pszContext )
  430. {
  431. Assert( pszContext );
  432. Assert( pSOCache );
  433. Assert( pObject );
  434. if ( pSOCache == NULL || pObject == NULL )
  435. {
  436. SetError( CFmtStr( "%s: attempt to manipulate invalid SO cache %s, object %s", pszContext, pSOCache ? pSOCache->GetOwner().Render() : "[none]", pObject ? pObject->GetDebugString().String() : "[none]" ) );
  437. return false;
  438. }
  439. const bool bSOCachedLocked = GGCBase()->IsSteamIDLockedByCurJob( pSOCache->GetOwner() );
  440. Assert( bSOCachedLocked );
  441. if ( !bSOCachedLocked )
  442. {
  443. SetError( CFmtStr( "%s: attempt to manipulate non-locked SO cache %s to add object %s", pszContext, pSOCache->GetOwner().Render(), pObject->GetDebugString().String() ) );
  444. return false;
  445. }
  446. return pSOCache != NULL && pObject != NULL && bSOCachedLocked;
  447. }
  448. void CSharedObjectTransaction::AddManagedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
  449. {
  450. if ( !AssertValidInput( pSOCache, pObject, "AddManagedObject()" ) )
  451. return;
  452. // if an object is in the added list, we're going to do a full write so modifying it at this point adds nothing
  453. // new; if an object is in the modified list, we already tracked the initial state; either way we have no new
  454. // data to track here
  455. if ( FindObjectInVector( pObject, m_vecObjects_Added ) || FindObjectInVector( pObject, m_vecObjects_Modified ) )
  456. return;
  457. undoinfo_t info = { pObject, pSOCache, NULL };
  458. info.pOriginalCopy = CSharedObject::Create( pObject->GetTypeID() );
  459. info.pOriginalCopy->Copy( *pObject );
  460. m_vecObjects_Modified.AddToTail( info );
  461. }
  462. void CSharedObjectTransaction::AddNewObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
  463. {
  464. if ( !AssertValidInput( pSOCache, pObject, "AddNewObject()" ) )
  465. return;
  466. if ( FindObjectInVector( pObject, m_vecObjects_Added ) )
  467. return;
  468. undoinfo_t info = { pObject, pSOCache, NULL };
  469. m_vecObjects_Added.AddToTail( info );
  470. }
  471. void CSharedObjectTransaction::RemoveObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject )
  472. {
  473. if ( !AssertValidInput( pSOCache, pObject, "RemoveObject()" ) )
  474. return;
  475. // make sure the object we're removing is in the cache we're trying to remove it from.
  476. AssertMsg1( pSOCache->FindSharedObject( *pObject ), "Attempting to remove object '%s' from non-owning cache!", pObject->GetDebugString().Get() );
  477. // look through our lists of objects that we're adding and objects that we're modifying. If we're
  478. // removing an object that we're adding in the same transaction through the same pointer, this will
  479. // result in a broken SO cache. Removing a modified object may or may not be safe but it's probably
  480. // indicative of a higher-level logic bug regardless.
  481. if ( undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Added ) )
  482. {
  483. EmitError( SPEW_GC, "Attempting to add and remove the same object from the same CSharedObjectTransaction! Object: %s", pInfo->pObject->GetDebugString().Get() );
  484. Assert( !"Attempting to add and remove the same object from the same CSharedObjectTransaction!" );
  485. m_vecObjects_Added.FindAndFastRemove( *pInfo );
  486. }
  487. if ( undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Modified ) )
  488. {
  489. EmitError( SPEW_GC, "Attempting to modify and remove the same object from the same CSharedObjectTransaction! Object: %s", pInfo->pObject->GetDebugString().Get() );
  490. Assert( !"Attempting to modify and remove the same object from the same CSharedObjectTransaction!" );
  491. m_vecObjects_Modified.FindAndFastRemove( *pInfo );
  492. }
  493. // @note Tom Bui: the act of removing an item may change the object, so to roll that back,
  494. // we need the original version
  495. undoinfo_t info = { pObject, pSOCache, NULL };
  496. info.pOriginalCopy = CSharedObject::Create( pObject->GetTypeID() );
  497. info.pOriginalCopy->Copy( *pObject );
  498. m_vecObjects_Removed.AddToTail( info );
  499. if ( !pObject->BYieldingAddRemoveToTransaction( m_sqlAccess ) )
  500. {
  501. SetError( "RemoveObject(): BYieldingAddRemoveToTransaction() failed" );
  502. return;
  503. }
  504. }
  505. void CSharedObjectTransaction::ModifiedObject( CGCSharedObjectCache *pSOCache, CSharedObject *pObject, uint32 unFieldIdx )
  506. {
  507. if ( !AssertValidInput( pSOCache, pObject, "ModifiedObject()" ) )
  508. return;
  509. // look for an object in the transaction -- this might be a new object created for this
  510. // transaction or it might be an object we've already tagged for modification; we don't
  511. // use FindSharedObject() for this because we might not be in an SO cache yet
  512. // if we're in the add list, we aren't intended to be in a cache yet, and so we also don't
  513. // have to dirty any fields -- we don't exist for real yet so when we finalize this transaction,
  514. // effectively *everything* is dirty
  515. if ( FindObjectInVector( pObject, m_vecObjects_Added ) )
  516. return;
  517. // make sure the object we're removing is in the cache we think it is. This check has to happen
  518. // after the "is in the added list?" check above because we won't actually put items in the cache
  519. // for real until after the SQL transaction succeeds
  520. AssertMsg1( pSOCache->FindSharedObject( *pObject ), "Attempting to modify object '%s' in non-owning cache!", pObject->GetDebugString().Get() );
  521. undoinfo_t *pInfo = FindObjectInVector( pObject, m_vecObjects_Modified );
  522. if ( pInfo )
  523. {
  524. pInfo->pSOCache->DirtyObjectField( pObject, unFieldIdx );
  525. return;
  526. }
  527. Assert( !"Attempt to modify an unmanaged object in CSharedObjectTransaction!" );
  528. SetError( CFmtStr( "ModifiedObject(): attempt to modify an unmanaged object %s", pObject->GetDebugString().String() ) );
  529. }
  530. CSharedObject *CSharedObjectTransaction::FindSharedObject( CGCSharedObjectCache *pSOCache, const CSharedObject &soIndex )
  531. {
  532. // search in modified objects
  533. FOR_EACH_VEC( m_vecObjects_Modified, i )
  534. {
  535. undoinfo_t &info = m_vecObjects_Modified[i];
  536. if ( info.pSOCache == pSOCache && info.pObject->BIsKeyEqual( soIndex ) )
  537. return info.pObject;
  538. }
  539. // search in new objects
  540. FOR_EACH_VEC( m_vecObjects_Added, i )
  541. {
  542. undoinfo_t &info = m_vecObjects_Added[i];
  543. if ( info.pSOCache == pSOCache && info.pObject->BIsKeyEqual( soIndex ) )
  544. return info.pObject;
  545. }
  546. return NULL;
  547. }
  548. void CSharedObjectTransaction::Rollback()
  549. {
  550. if ( m_sqlAccess.BInTransaction() )
  551. {
  552. m_sqlAccess.RollbackTransaction();
  553. }
  554. Undo();
  555. }
  556. bool CSharedObjectTransaction::BYieldingCommit( bool bAllowEmpty )
  557. {
  558. const char *pszPreExistingError = GetError();
  559. if ( pszPreExistingError )
  560. {
  561. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': %s.\n", PchName(), pszPreExistingError );
  562. Undo();
  563. return false;
  564. }
  565. Assert( m_sqlAccess.BInTransaction() );
  566. if ( !m_sqlAccess.BInTransaction() )
  567. {
  568. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': transaction closed before commit!\n", PchName() );
  569. Undo();
  570. return false;
  571. }
  572. bool bSuccess = true;
  573. bool bDBRelevantObjectsChanged = false;
  574. // add insert statements to sql access
  575. FOR_EACH_VEC( m_vecObjects_Added, i )
  576. {
  577. undoinfo_t &info = m_vecObjects_Added[i];
  578. if ( info.pObject->BIsDatabaseBacked() )
  579. {
  580. bSuccess &= info.pObject->BYieldingAddInsertToTransaction( m_sqlAccess );
  581. bDBRelevantObjectsChanged = true;
  582. }
  583. }
  584. // add update statements to sql access
  585. FOR_EACH_VEC( m_vecObjects_Modified, i )
  586. {
  587. undoinfo_t &info = m_vecObjects_Modified[i];
  588. if ( info.pObject->BIsDatabaseBacked() )
  589. {
  590. bSuccess &= info.pSOCache->BYieldingAddWriteToTransaction( info.pObject, m_sqlAccess );
  591. bDBRelevantObjectsChanged = true;
  592. }
  593. }
  594. if ( bSuccess == false )
  595. {
  596. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': failed to add inserts/writes.\n", PchName() );
  597. Undo();
  598. return false;
  599. }
  600. // try to commit db transaction.
  601. if ( m_sqlAccess.BCommitTransaction( bAllowEmpty && !bDBRelevantObjectsChanged ) == false )
  602. {
  603. EmitError( SPEW_GC, "Failed to commit CSharedObjectTransaction '%s': SQL transaction failure.\n", PchName() );
  604. Undo();
  605. return false;
  606. }
  607. // remove objects from SO cache
  608. FOR_EACH_VEC( m_vecObjects_Removed, i )
  609. {
  610. undoinfo_t &info = m_vecObjects_Removed[i];
  611. DbgVerify( info.pSOCache->BDestroyObject( *info.pObject, false ) ); // internally will assert if cache isn't locked
  612. delete info.pOriginalCopy;
  613. }
  614. m_vecObjects_Removed.RemoveAll();
  615. // add new objects to SO cache
  616. FOR_EACH_VEC( m_vecObjects_Added, i )
  617. {
  618. undoinfo_t &info = m_vecObjects_Added[i];
  619. info.pSOCache->AddObject( info.pObject ); // internally will assert if cache isn't locked
  620. Assert( info.pOriginalCopy == NULL );
  621. }
  622. // free up memory for original state of modified objects
  623. FOR_EACH_VEC( m_vecObjects_Modified, i )
  624. {
  625. undoinfo_t &info = m_vecObjects_Modified[i];
  626. info.pSOCache->DirtyNetworkObject( info.pObject );
  627. delete info.pOriginalCopy;
  628. }
  629. // send network updates for objects that were added or modified
  630. // this is OK to call more than once on a CGCSharedObjectCache, because internally
  631. // it keeps a list of things that were marked dirty
  632. FOR_EACH_VEC( m_vecObjects_Added, i )
  633. {
  634. m_vecObjects_Added[i].pSOCache->SendAllNetworkUpdates();
  635. }
  636. FOR_EACH_VEC( m_vecObjects_Modified, i )
  637. {
  638. m_vecObjects_Modified[i].pSOCache->SendAllNetworkUpdates();
  639. }
  640. m_vecObjects_Added.RemoveAll();
  641. m_vecObjects_Modified.RemoveAll();
  642. return true;
  643. }
  644. void CSharedObjectTransaction::Undo()
  645. {
  646. FOR_EACH_VEC( m_vecObjects_Added, i )
  647. {
  648. undoinfo_t &info = m_vecObjects_Added[i];
  649. delete info.pObject;
  650. }
  651. m_vecObjects_Added.RemoveAll();
  652. FOR_EACH_VEC( m_vecObjects_Removed, i )
  653. {
  654. undoinfo_t &info = m_vecObjects_Removed[i];
  655. AssertMsg1( GGCBase()->IsSteamIDLockedByCurJob( info.pSOCache->GetOwner() ), "Attempt to modify in-memory object '%s' during CSharedObjectTransaction removal rollback.", info.pObject->GetDebugString().Get() );
  656. info.pObject->Copy( *info.pOriginalCopy );
  657. delete info.pOriginalCopy;
  658. }
  659. m_vecObjects_Removed.RemoveAll();
  660. FOR_EACH_VEC( m_vecObjects_Modified, i )
  661. {
  662. undoinfo_t &info = m_vecObjects_Modified[i];
  663. AssertMsg1( GGCBase()->IsSteamIDLockedByCurJob( info.pSOCache->GetOwner() ), "Attempt to modify in-memory object '%s' during CSharedObjectTransaction modify rollback.", info.pObject->GetDebugString().Get() );
  664. info.pObject->Copy( *info.pOriginalCopy );
  665. delete info.pOriginalCopy;
  666. }
  667. m_vecObjects_Modified.RemoveAll();
  668. ClearError();
  669. }
  670. const char *CSharedObjectTransaction::PchName() const
  671. {
  672. return m_sqlAccess.PchTransactionName();
  673. }
  674. };