public function shutdown() { if ($this->mainLB) { $this->chronProt->shutdownLB($this->mainLB); } foreach ($this->extLBs as $extLB) { $this->chronProt->shutdownLB($extLB); } $this->chronProt->shutdown(); $this->commitMasterChanges(); }
/** * @param ChronologyProtector $cp */ protected function shutdownChronologyProtector(ChronologyProtector $cp) { // Get all the master positions needed $this->forEachLB(function (LoadBalancer $lb) use($cp) { $cp->shutdownLB($lb); }); // Write them to the stash $unsavedPositions = $cp->shutdown(); // If the positions failed to write to the stash, at least wait on local datacenter // slaves to catch up before responding. Even if there are several DCs, this increases // the chance that the user will see their own changes immediately afterwards. As long // as the sticky DC cookie applies (same domain), this is not even an issue. $this->forEachLB(function (LoadBalancer $lb) use($unsavedPositions) { $masterName = $lb->getServerName($lb->getWriterIndex()); if (isset($unsavedPositions[$masterName])) { $lb->waitForAll($unsavedPositions[$masterName]); } }); }
/** * Get and record all of the staged DB positions into persistent memory storage * * @param ChronologyProtector $cp * @param callable|null $workCallback Work to do instead of waiting on syncing positions * @param string $mode One of (sync, async); whether to wait on remote datacenters */ protected function shutdownChronologyProtector(ChronologyProtector $cp, $workCallback, $mode) { // Record all the master positions needed $this->forEachLB(function (ILoadBalancer $lb) use($cp) { $cp->shutdownLB($lb); }); // Write them to the persistent stash. Try to do something useful by running $work // while ChronologyProtector waits for the stash write to replicate to all DCs. $unsavedPositions = $cp->shutdown($workCallback, $mode); if ($unsavedPositions && $workCallback) { // Invoke callback in case it did not cache the result yet $workCallback(); // work now to block for less time in waitForAll() } // If the positions failed to write to the stash, at least wait on local datacenter // replica DBs to catch up before responding. Even if there are several DCs, this increases // the chance that the user will see their own changes immediately afterwards. As long // as the sticky DC cookie applies (same domain), this is not even an issue. $this->forEachLB(function (ILoadBalancer $lb) use($unsavedPositions) { $masterName = $lb->getServerName($lb->getWriterIndex()); if (isset($unsavedPositions[$masterName])) { $lb->waitForAll($unsavedPositions[$masterName]); } }); }
public function testChronologyProtector() { // (a) First HTTP request $mPos = new MySQLMasterPos('db1034-bin.000976', '843431247'); $mockDB = $this->getMockBuilder('DatabaseMysql')->disableOriginalConstructor()->getMock(); $mockDB->expects($this->any())->method('doneWrites')->will($this->returnValue(true)); $mockDB->expects($this->any())->method('getMasterPos')->will($this->returnValue($mPos)); $lb = $this->getMockBuilder('LoadBalancer')->disableOriginalConstructor()->getMock(); $lb->expects($this->any())->method('getConnection')->will($this->returnValue($mockDB)); $lb->expects($this->any())->method('getServerCount')->will($this->returnValue(2)); $lb->expects($this->any())->method('parentInfo')->will($this->returnValue(array('id' => "main-DEFAULT"))); $lb->expects($this->any())->method('getAnyOpenConnection')->will($this->returnValue($mockDB)); $bag = new HashBagOStuff(); $cp = new ChronologyProtector($bag, array('ip' => '127.0.0.1', 'agent' => "Totally-Not-FireFox")); $mockDB->expects($this->exactly(2))->method('doneWrites'); // Nothing to wait for $cp->initLB($lb); // Record in stash $cp->shutdownLB($lb); $cp->shutdown(); // (b) Second HTTP request $cp = new ChronologyProtector($bag, array('ip' => '127.0.0.1', 'agent' => "Totally-Not-FireFox")); $lb->expects($this->once())->method('waitFor')->with($this->equalTo($mPos)); // Wait $cp->initLB($lb); // Record in stash $cp->shutdownLB($lb); $cp->shutdown(); }
public function testChronologyProtector() { // (a) First HTTP request $mPos = new MySQLMasterPos('db1034-bin.000976', '843431247'); $now = microtime(true); $mockDB = $this->getMockBuilder('DatabaseMysql')->disableOriginalConstructor()->getMock(); $mockDB->method('writesOrCallbacksPending')->willReturn(true); $mockDB->method('lastDoneWrites')->willReturn($now); $mockDB->method('getMasterPos')->willReturn($mPos); $lb = $this->getMockBuilder('LoadBalancer')->disableOriginalConstructor()->getMock(); $lb->method('getConnection')->willReturn($mockDB); $lb->method('getServerCount')->willReturn(2); $lb->method('parentInfo')->willReturn(['id' => "main-DEFAULT"]); $lb->method('getAnyOpenConnection')->willReturn($mockDB); $lb->method('hasOrMadeRecentMasterChanges')->will($this->returnCallback(function () use($mockDB) { $p = 0; $p |= call_user_func([$mockDB, 'writesOrCallbacksPending']); $p |= call_user_func([$mockDB, 'lastDoneWrites']); return (bool) $p; })); $lb->method('getMasterPos')->willReturn($mPos); $bag = new HashBagOStuff(); $cp = new ChronologyProtector($bag, ['ip' => '127.0.0.1', 'agent' => "Totally-Not-FireFox"]); $mockDB->expects($this->exactly(2))->method('writesOrCallbacksPending'); $mockDB->expects($this->exactly(2))->method('lastDoneWrites'); // Nothing to wait for $cp->initLB($lb); // Record in stash $cp->shutdownLB($lb); $cp->shutdown(); // (b) Second HTTP request $cp = new ChronologyProtector($bag, ['ip' => '127.0.0.1', 'agent' => "Totally-Not-FireFox"]); $lb->expects($this->once())->method('waitFor')->with($this->equalTo($mPos)); // Wait $cp->initLB($lb); // Record in stash $cp->shutdownLB($lb); $cp->shutdown(); }