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.

455 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "sv_sessionblockpublisher.h"
  5. #include "sv_replaycontext.h"
  6. #include "demofile/demoformat.h"
  7. #include "sv_recordingsession.h"
  8. #include "sv_recordingsessionblock.h"
  9. #include "sv_sessioninfopublisher.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //----------------------------------------------------------------------------------------
  13. CSessionBlockPublisher::CSessionBlockPublisher( CServerRecordingSession *pSession,
  14. CSessionInfoPublisher *pSessionInfoPublisher )
  15. : m_pSession( pSession ),
  16. m_pSessionInfoPublisher( pSessionInfoPublisher )
  17. {
  18. // Cache the dump interval so it can't be modified during a round - doing so would require
  19. // an update on all clients.
  20. extern ConVar replay_block_dump_interval;
  21. m_nDumpInterval = MAX( MIN_SERVER_DUMP_INTERVAL, replay_block_dump_interval.GetInt() );
  22. // Write the first block 15 or so seconds from now
  23. m_flLastBlockWriteTime = g_pEngine->GetHostTime();
  24. }
  25. CSessionBlockPublisher::~CSessionBlockPublisher()
  26. {
  27. }
  28. void CSessionBlockPublisher::PublishAllSynchronous()
  29. {
  30. while ( !IsDone() )
  31. {
  32. Think();
  33. }
  34. }
  35. void CSessionBlockPublisher::AbortPublish()
  36. {
  37. FOR_EACH_LL( m_lstPublishingBlocks, it )
  38. {
  39. CServerRecordingSessionBlock *pCurBlock = m_lstPublishingBlocks[ it ];
  40. IFilePublisher *&pPublisher = pCurBlock->m_pPublisher; // Shorthand
  41. if ( !pPublisher )
  42. continue;
  43. // Already done?
  44. if ( pPublisher->IsDone() )
  45. continue;
  46. pPublisher->AbortAndCleanup();
  47. }
  48. // Remove all blocks
  49. m_lstPublishingBlocks.RemoveAll();
  50. }
  51. void CSessionBlockPublisher::OnStartRecording()
  52. {
  53. }
  54. void CSessionBlockPublisher::OnStopRecord( bool bAborting )
  55. {
  56. if ( !bAborting )
  57. {
  58. // Write one final session block.
  59. WriteAndPublishSessionBlock();
  60. }
  61. }
  62. ReplayHandle_t CSessionBlockPublisher::GetSessionHandle() const
  63. {
  64. return m_pSession->GetHandle();
  65. }
  66. void CSessionBlockPublisher::WriteAndPublishSessionBlock()
  67. {
  68. // Make sure there is data to write
  69. uint8 *pSessionBuffer;
  70. int nSessionBufferSize;
  71. g_pEngine->GetSessionRecordBuffer( &pSessionBuffer, &nSessionBufferSize ); // This will get called the last client disconnects from the server - but in waiting for players state we won't have a demo buffer
  72. if ( !pSessionBuffer || nSessionBufferSize == 0 )
  73. return;
  74. // Create a new block
  75. CServerRecordingSessionBlock *pNewBlock = SV_CastBlock( SV_GetRecordingSessionBlockManager()->CreateAndGenerateHandle() );
  76. if ( !pNewBlock )
  77. {
  78. Warning( "Failed to create replay \"%s\"\n", pNewBlock->m_szFullFilename );
  79. delete pNewBlock;
  80. return;
  81. }
  82. if ( m_pSession->m_nServerStartRecordTick < 0 )
  83. {
  84. Warning( "Error: Current recording start tick was not properly setup. Aborting block write.\n" );
  85. delete pNewBlock;
  86. return;
  87. }
  88. // Figure out what the current block is
  89. const int iCurrentSessionBlock = m_pSession->GetNumBlocks();
  90. // Add an entry to the server index with the "writing" status set
  91. const char *pFullFilename = Replay_va(
  92. "%s%s_part_%u.%s", SV_GetTmpDir(),
  93. SV_GetRecordingSessionManager()->GetCurrentSessionName(), iCurrentSessionBlock, BLOCK_FILE_EXTENSION
  94. );
  95. V_strcpy( pNewBlock->m_szFullFilename, pFullFilename );
  96. pNewBlock->m_nWriteStatus = CServerRecordingSessionBlock::WRITESTATUS_INVALID; // Must be set here to trigger write
  97. pNewBlock->m_nRemoteStatus = CBaseRecordingSessionBlock::STATUS_WRITING;
  98. pNewBlock->m_iReconstruction = iCurrentSessionBlock;
  99. pNewBlock->m_hSession = m_pSession->GetHandle();
  100. // Match the session's lock - the block will be unlocked once recording has stopped and all publishing is complete.
  101. pNewBlock->SetLocked( true );
  102. // Commit the replay to the history manager's list
  103. SV_GetRecordingSessionBlockManager()->Add( pNewBlock );
  104. // Also store a pointer to the block in the session - NOTE: session will not attempt to free this pointer
  105. m_pSession->AddBlock( pNewBlock, false );
  106. // Cache the block temporarily while the binary block itself writes to disk - NOTE: will not attempt to free
  107. m_lstPublishingBlocks.AddToTail( pNewBlock );
  108. // Write the block now
  109. PublishBlock( pNewBlock ); // pNewBlock->m_nWriteStatus modified here
  110. IF_REPLAY_DBG( Warning( "%f: (%i) Publishing new block, %s\n", g_pEngine->GetHostTime(), iCurrentSessionBlock, pNewBlock->GetFilename() ) );
  111. }
  112. void CSessionBlockPublisher::GatherBlockData( uint8 *pSessionBuffer, int nSessionBufferSize, CServerRecordingSessionBlock *pBlock, unsigned char **ppSafeBlockData, int *pBlockSize )
  113. {
  114. const int nHeaderSize = sizeof( demoheader_t );
  115. int nBlockOffset = 0;
  116. const int nBlockSize = nSessionBufferSize;
  117. int nTotalSize = nBlockSize;
  118. demoheader_t *pHeader = NULL;
  119. // If this is the first block, pass in a header to be written. Otherwise, just write the block.
  120. if ( !pBlock->m_iReconstruction )
  121. {
  122. // Setup start tick in the header
  123. pHeader = g_pEngine->GetReplayDemoHeader();
  124. // Add header size
  125. nBlockOffset = nHeaderSize;
  126. nTotalSize += nHeaderSize;
  127. }
  128. // Make a copy of the block
  129. unsigned char *pBuffer = new unsigned char[ nTotalSize ];
  130. unsigned char *pBlockCopy = pBuffer + nBlockOffset;
  131. // Only write the header if necessary
  132. if ( pHeader )
  133. {
  134. demoheader_t littleEndianHeader = *pHeader;
  135. littleEndianHeader.playback_time = FLT_MAX;
  136. littleEndianHeader.playback_ticks = INT_MAX;
  137. littleEndianHeader.playback_frames = INT_MAX;
  138. // Byteswap
  139. ByteSwap_demoheader_t( littleEndianHeader );
  140. // Write header
  141. V_memcpy( pBuffer, &littleEndianHeader, sizeof( littleEndianHeader ) );
  142. }
  143. // Note that pBlockCopy is based on pBuffer, which was allocated with nBlockSize PLUS
  144. // header size - this will not overflow.
  145. V_memcpy( pBlockCopy, pSessionBuffer, nBlockSize );
  146. // Copy to "out" parameters
  147. *pBlockSize = nTotalSize;
  148. *ppSafeBlockData = pBuffer;
  149. }
  150. void CSessionBlockPublisher::PublishBlock( CServerRecordingSessionBlock *pBlock )
  151. {
  152. uint8 *pSessionBuffer;
  153. int nSessionBufferSize;
  154. if ( !g_pEngine->GetSessionRecordBuffer( &pSessionBuffer, &nSessionBufferSize ) )
  155. {
  156. Warning( "Block publish failed!\n" );
  157. return;
  158. }
  159. unsigned char *pSafeBlockData;
  160. int nBlockSize;
  161. GatherBlockData( pSessionBuffer, nSessionBufferSize, pBlock, &pSafeBlockData, &nBlockSize );
  162. // We've got what we need and can reset the put ptr
  163. g_pEngine->ResetReplayRecordBuffer();
  164. AssertMsg( !pBlock->m_pPublisher, "No publisher should exist for this block yet!" );
  165. // Set status to working
  166. pBlock->m_nWriteStatus = CServerRecordingSessionBlock::WRITESTATUS_WORKING;
  167. // Get the number of bytes written
  168. pBlock->m_uFileSize = nBlockSize;
  169. // Make sure the main thread doesn't unload the block while it's being published
  170. pBlock->SetLocked( true );
  171. // Asynchronously publish to fileserver
  172. PublishFileParams_t params;
  173. params.m_pOutFilename = pBlock->m_szFullFilename;
  174. params.m_pSrcData = pSafeBlockData;
  175. params.m_nSrcSize = nBlockSize;
  176. params.m_pCallbackHandler = this;
  177. params.m_nCompressorType = COMPRESSORTYPE_BZ2;
  178. params.m_bHash = true;
  179. params.m_bFreeSrcData = true;
  180. params.m_bDeleteFile = false;
  181. params.m_pUserData = pBlock;
  182. pBlock->m_pPublisher = SV_PublishFile( params );
  183. }
  184. void CSessionBlockPublisher::OnPublishComplete( const IFilePublisher *pPublisher, void *pUserData )
  185. {
  186. CServerRecordingSessionBlock *pBlock = (CServerRecordingSessionBlock *)pUserData;
  187. Assert( pBlock );
  188. // Set block status
  189. if ( pPublisher->GetStatus() == IFilePublisher::PUBLISHSTATUS_OK )
  190. {
  191. pBlock->m_nWriteStatus = CServerRecordingSessionBlock::WRITESTATUS_SUCCESS;
  192. }
  193. else
  194. {
  195. pBlock->m_nWriteStatus = CServerRecordingSessionBlock::WRITESTATUS_FAILED;
  196. // Publish failed - handle as needed
  197. g_pServerReplayContext->OnPublishFailed();
  198. }
  199. // Did the block compress OK?
  200. if ( pPublisher->Compressed() )
  201. {
  202. // Cache compressor type
  203. pBlock->m_nCompressorType = pPublisher->GetCompressorType();
  204. const int nCompressedSize = pPublisher->GetCompressedSize();
  205. const float flRatio = (float)pBlock->m_uFileSize / nCompressedSize;
  206. IF_REPLAY_DBG( Warning( "Block compression ratio: %.3f:1\n", flRatio ) );
  207. // Update size
  208. pBlock->m_uUncompressedSize = pBlock->m_uFileSize;
  209. pBlock->m_uFileSize = nCompressedSize;
  210. }
  211. // Get the MD5
  212. if ( pPublisher->Hashed() )
  213. {
  214. pPublisher->GetHash( pBlock->m_aHash );
  215. }
  216. // Now that m_nWriteStatus has been set in the block, the session info will be updated
  217. // accordingly the next time PublishThink() is run.
  218. // Mark the block as dirty since it was modified
  219. Assert( pBlock->m_nWriteStatus != CServerRecordingSessionBlock::WRITESTATUS_INVALID );
  220. SV_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false );
  221. IF_REPLAY_DBG( Warning( "Publish complete for block %s\n", pBlock->GetDebugName() ) );
  222. }
  223. void CSessionBlockPublisher::OnPublishAborted( const IFilePublisher *pPublisher )
  224. {
  225. CServerRecordingSessionBlock *pBlock = FindBlockFromPublisher( pPublisher );
  226. // Update the block's status
  227. if ( pBlock )
  228. {
  229. pBlock->m_nWriteStatus = CServerRecordingSessionBlock::WRITESTATUS_FAILED;
  230. }
  231. g_pServerReplayContext->OnPublishFailed();
  232. }
  233. CServerRecordingSessionBlock *CSessionBlockPublisher::FindBlockFromPublisher( const IFilePublisher *pPublisher )
  234. {
  235. FOR_EACH_LL( m_lstPublishingBlocks, i )
  236. {
  237. CServerRecordingSessionBlock *pCurBlock = m_lstPublishingBlocks[ i ];
  238. if ( pCurBlock->m_pPublisher == pPublisher )
  239. {
  240. return pCurBlock;
  241. }
  242. }
  243. AssertMsg( 0, "Could not find block with the given publisher!" );
  244. return NULL;
  245. }
  246. void CSessionBlockPublisher::Think()
  247. {
  248. // NOTE: This member function gets called even if replay is disabled. This is intentional.
  249. VPROF_BUDGET( "CSessionBlockPublisher::Think", VPROF_BUDGETGROUP_REPLAY );
  250. PublishThink();
  251. }
  252. void CSessionBlockPublisher::PublishThink()
  253. {
  254. AssertMsg( m_pSession->IsLocked(), "The session isn't locked, which means blocks can be being deleted and will probably cause a crash." );
  255. // Go through all currently publishing blocks and free/think
  256. FOR_EACH_LL( m_lstPublishingBlocks, it )
  257. {
  258. CServerRecordingSessionBlock *pCurBlock = m_lstPublishingBlocks[ it ];
  259. IFilePublisher *&pPublisher = pCurBlock->m_pPublisher; // Shorthand
  260. if ( !pPublisher )
  261. continue;
  262. // If the publisher's done, free it
  263. if ( pPublisher->IsDone() )
  264. {
  265. delete pPublisher;
  266. pPublisher = NULL;
  267. }
  268. else
  269. {
  270. // Let the publisher think
  271. pPublisher->Think();
  272. }
  273. }
  274. // Write a new session block out right now?
  275. float flHostTime = g_pEngine->GetHostTime();
  276. if ( m_flLastBlockWriteTime != 0.0f &&
  277. flHostTime - m_flLastBlockWriteTime >= m_nDumpInterval &&
  278. m_pSession->m_bRecording )
  279. {
  280. Assert( m_nDumpInterval > 0 );
  281. // Write it
  282. WriteAndPublishSessionBlock();
  283. // Update the time
  284. m_flLastBlockWriteTime = flHostTime;
  285. }
  286. // Check status of any replays that are being written
  287. bool bUpdateSessionInfo = false;
  288. for( int it = m_lstPublishingBlocks.Head(); it != m_lstPublishingBlocks.InvalidIndex(); )
  289. {
  290. CServerRecordingSessionBlock *pCurBlock = m_lstPublishingBlocks[ it ];
  291. // Updated when write status is set to success or failure
  292. int nPendingRequestStatus = CBaseRecordingSessionBlock::STATUS_INVALID;
  293. // If set to anything besides InvalidIndex(), it will be removed from the list
  294. int itRemove = m_lstPublishingBlocks.InvalidIndex();
  295. bool bWriteBlockInfoToDisk = false;
  296. switch ( pCurBlock->m_nWriteStatus )
  297. {
  298. case CServerRecordingSessionBlock::WRITESTATUS_INVALID:
  299. AssertMsg( 0, "Why is m_nWriteStatus WRITESTATUS_INVALID here?" );
  300. break;
  301. case CServerRecordingSessionBlock::WRITESTATUS_WORKING: // Do nothing if still writing
  302. break;
  303. case CServerRecordingSessionBlock::WRITESTATUS_SUCCESS:
  304. IF_REPLAY_DBG2( Warning( " Block %i marked as succeeded.\n", pCurBlock->m_iReconstruction ) );
  305. pCurBlock->m_nRemoteStatus = CBaseRecordingSessionBlock::STATUS_READYFORDOWNLOAD;
  306. nPendingRequestStatus = pCurBlock->m_nRemoteStatus;
  307. bWriteBlockInfoToDisk = true;
  308. itRemove = it;
  309. break;
  310. case CServerRecordingSessionBlock::WRITESTATUS_FAILED:
  311. default: // Error?
  312. IF_REPLAY_DBG2( Warning( " Block %i marked as failed.\n", pCurBlock->m_iReconstruction ) );
  313. pCurBlock->m_nRemoteStatus = CBaseRecordingSessionBlock::STATUS_ERROR;
  314. pCurBlock->m_nHttpError = CBaseRecordingSessionBlock::ERROR_WRITEFAILED;
  315. nPendingRequestStatus = pCurBlock->m_nRemoteStatus;
  316. bWriteBlockInfoToDisk = true;
  317. itRemove = it;
  318. // TODO: Retry
  319. }
  320. if ( bWriteBlockInfoToDisk )
  321. {
  322. // Save the master index file
  323. Assert( pCurBlock->m_nWriteStatus != CServerRecordingSessionBlock::WRITESTATUS_INVALID );
  324. SV_GetRecordingSessionBlockManager()->FlagForFlush( pCurBlock, false );
  325. }
  326. // Find the owning session
  327. Assert( pCurBlock->m_hSession == m_pSession->GetHandle() );
  328. // Refresh session info file
  329. if ( nPendingRequestStatus != CBaseRecordingSessionBlock::STATUS_INVALID )
  330. {
  331. // Update it after this loop
  332. bUpdateSessionInfo = true;
  333. }
  334. // Update iterator
  335. it = m_lstPublishingBlocks.Next( it );
  336. // Remove?
  337. if ( itRemove != m_lstPublishingBlocks.InvalidIndex() )
  338. {
  339. IF_REPLAY_DBG( Warning( "Removing block %i from publisher\n", pCurBlock->m_iReconstruction ) );
  340. // Free/clear publisher
  341. delete pCurBlock->m_pPublisher;
  342. pCurBlock->m_pPublisher = NULL;
  343. // Removes from the list but doesn't free, since any pointer here points to a block somewhere
  344. m_lstPublishingBlocks.Unlink( itRemove );
  345. }
  346. }
  347. // Publish session info file now if it isn't already publishing
  348. if ( bUpdateSessionInfo )
  349. {
  350. m_pSessionInfoPublisher->Publish();
  351. }
  352. }
  353. bool CSessionBlockPublisher::IsDone() const
  354. {
  355. return m_lstPublishingBlocks.Count() == 0;
  356. }
  357. #ifdef _DEBUG
  358. void CSessionBlockPublisher::Validate()
  359. {
  360. FOR_EACH_LL( m_lstPublishingBlocks, i )
  361. {
  362. CServerRecordingSessionBlock *pCurBlock = m_lstPublishingBlocks[ i ];
  363. Assert( pCurBlock->m_nRemoteStatus == CBaseRecordingSessionBlock::STATUS_READYFORDOWNLOAD );
  364. }
  365. }
  366. #endif
  367. //----------------------------------------------------------------------------------------