Set ignore_user_abort( true ) during DB commit for sanity
authorAaron Schulz <aschulz@wikimedia.org>
Sat, 24 Sep 2016 06:03:56 +0000 (23:03 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Sat, 24 Sep 2016 06:03:56 +0000 (23:03 -0700)
MediaWiki already does this in preOutputCommit(), but it should
also be in the DB layer.

Change-Id: I3193c236a7823f27938077b372887867aeaf78c5

includes/libs/rdbms/chronologyprotector/ChronologyProtector.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php

index 94a3b6c..1f9aff1 100644 (file)
@@ -27,7 +27,7 @@ use Psr\Log\LoggerInterface;
  * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
  * Kind of like Hawking's [[Chronology Protection Agency]].
  */
-class ChronologyProtector implements LoggerAwareInterface{
+class ChronologyProtector implements LoggerAwareInterface {
        /** @var BagOStuff */
        protected $store;
        /** @var LoggerInterface */
index d7ca7cd..9c9f18d 100644 (file)
@@ -136,9 +136,9 @@ interface ILBFactory {
        public function flushReplicaSnapshots( $fname = __METHOD__ );
 
        /**
-        * Commit on all connections. Done for two reasons:
-        * 1. To commit changes to the masters.
-        * 2. To release the snapshot on all connections, master and replica DB.
+        * Commit open transactions on all connections. This is useful for two main cases:
+        *   - a) To commit changes to the masters.
+        *   - b) To release the snapshot on all connections, master and replica DBs.
         * @param string $fname Caller name
         * @param array $options Options map:
         *   - maxWriteDuration: abort if more than this much time was spent in write queries
index 85194bc..0635d04 100644 (file)
@@ -178,6 +178,8 @@ abstract class LBFactory implements ILBFactory {
                                "$fname: transaction round '{$this->trxRoundId}' still running."
                        );
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
                // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
                $this->forEachLBCallMethod( 'finalizeMasterChanges' );
                $this->trxRoundId = false;
@@ -516,6 +518,23 @@ abstract class LBFactory implements ILBFactory {
                $this->requestInfo = $info + $this->requestInfo;
        }
 
+       /**
+        * Make PHP ignore user aborts/disconnects until the returned
+        * value leaves scope. This returns null and does nothing in CLI mode.
+        *
+        * @return ScopedCallback|null
+        */
+       final protected function getScopedPHPBehaviorForCommit() {
+               if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
+                       $old = ignore_user_abort( true ); // avoid half-finished operations
+                       return new ScopedCallback( function () use ( $old ) {
+                               ignore_user_abort( $old );
+                       } );
+               }
+
+               return null;
+       }
+
        function __destruct() {
                $this->destroy();
        }
index 36e4c6c..7ad275a 100644 (file)
@@ -1101,6 +1101,9 @@ class LoadBalancer implements ILoadBalancer {
        public function commitMasterChanges( $fname = __METHOD__ ) {
                $failures = [];
 
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
+
                $restore = ( $this->trxRoundId !== false );
                $this->trxRoundId = false;
                $this->forEachOpenMasterConnection(
@@ -1516,6 +1519,23 @@ class LoadBalancer implements ILoadBalancer {
                } );
        }
 
+       /**
+        * Make PHP ignore user aborts/disconnects until the returned
+        * value leaves scope. This returns null and does nothing in CLI mode.
+        *
+        * @return ScopedCallback|null
+        */
+       final protected function getScopedPHPBehaviorForCommit() {
+               if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
+                       $old = ignore_user_abort( true ); // avoid half-finished operations
+                       return new ScopedCallback( function () use ( $old ) {
+                               ignore_user_abort( $old );
+                       } );
+               }
+
+               return null;
+       }
+
        function __destruct() {
                // Avoid connection leaks for sanity
                $this->closeAll();