-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transaction support #2465
Transaction support #2465
Changes from 11 commits
898f4e2
e68124c
eea9943
0a2e605
203160e
8fc6915
2c549c8
a6aa3cc
2a82a54
bb58fcb
df3a53a
f2510b1
8afe105
7a644bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,10 +44,6 @@ jobs: | |
- '8.0' | ||
- '8.1' | ||
services: | ||
mongo: | ||
image: mongo:${{ matrix.mongodb }} | ||
ports: | ||
- 27017:27017 | ||
mysql: | ||
image: mysql:5.7 | ||
ports: | ||
|
@@ -59,6 +55,13 @@ jobs: | |
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Create MongoDB Replica Set | ||
run: | | ||
docker run --name mongodb -p 27017:27017 -e MONGO_INITDB_DATABASE=unittest --detach mongo:${{ matrix.mongodb }} mongod --replSet rs | ||
until docker exec --tty mongodb mongo 127.0.0.1:27017 --eval "db.runCommand({ ping: 1 })"; do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just looking at the CI output. I'm not certain, but if this line is the only thing responsible for printing the exact MongoDB server version then perhaps we should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done - I added an additional build step for this to avoid people having to look through the entire setup output for this. |
||
sleep 1 | ||
done | ||
sudo docker exec --tty mongodb mongo 127.0.0.1:27017 --eval "rs.initiate({\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"127.0.0.1:27017\" }]})" | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- name: "Installing php" | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
|
@@ -88,7 +91,7 @@ jobs: | |
run: | | ||
./vendor/bin/phpunit --coverage-clover coverage.xml | ||
env: | ||
MONGODB_URI: 'mongodb://127.0.0.1/' | ||
MONGODB_URI: 'mongodb://127.0.0.1/?replicaSet=rs' | ||
MYSQL_HOST: 0.0.0.0 | ||
MYSQL_PORT: 3307 | ||
- uses: codecov/codecov-action@v1 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?php | ||
|
||
namespace Jenssegers\Mongodb\Concerns; | ||
|
||
use Closure; | ||
use MongoDB\Client; | ||
use MongoDB\Driver\Exception\RuntimeException; | ||
use MongoDB\Driver\Session; | ||
use function MongoDB\with_transaction; | ||
use Throwable; | ||
|
||
/** | ||
* @see https://docs.mongodb.com/manual/core/transactions/ | ||
*/ | ||
trait ManagesTransactions | ||
{ | ||
protected ?Session $session = null; | ||
|
||
protected $transactions = 0; | ||
|
||
/** | ||
* @return Client | ||
*/ | ||
abstract public function getMongoClient(); | ||
|
||
public function getSession(): ?Session | ||
{ | ||
return $this->session; | ||
} | ||
|
||
private function getSessionOrCreate(): Session | ||
{ | ||
if ($this->session === null) { | ||
$this->session = $this->getMongoClient()->startSession(); | ||
} | ||
|
||
return $this->session; | ||
} | ||
|
||
private function getSessionOrThrow(): Session | ||
{ | ||
$session = $this->getSession(); | ||
|
||
if ($session === null) { | ||
throw new RuntimeException('There is no active session.'); | ||
} | ||
|
||
return $session; | ||
} | ||
|
||
/** | ||
* Starts a transaction on the active session. An active session will be created if none exists. | ||
*/ | ||
public function beginTransaction(array $options = []): void | ||
{ | ||
$this->getSessionOrCreate()->startTransaction($options); | ||
$this->transactions = 1; | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* Commit transaction in this session. | ||
*/ | ||
public function commit(): void | ||
{ | ||
$this->getSessionOrThrow()->commitTransaction(); | ||
$this->transactions = 0; | ||
} | ||
|
||
/** | ||
* Abort transaction in this session. | ||
*/ | ||
public function rollBack($toLevel = null): void | ||
{ | ||
$this->getSessionOrThrow()->abortTransaction(); | ||
$this->transactions = 0; | ||
} | ||
|
||
/** | ||
* Static transaction function realize the with_transaction functionality provided by MongoDB. | ||
* | ||
* @param int $attempts | ||
*/ | ||
public function transaction(Closure $callback, $attempts = 1, array $options = []): mixed | ||
{ | ||
$attemptsLeft = $attempts; | ||
$callbackResult = null; | ||
$throwable = null; | ||
|
||
$callbackFunction = function (Session $session) use ($callback, &$attemptsLeft, &$callbackResult, &$throwable) { | ||
$attemptsLeft--; | ||
|
||
if ($attemptsLeft < 0) { | ||
$session->abortTransaction(); | ||
|
||
return; | ||
} | ||
|
||
// Catch, store and re-throw any exception thrown during execution | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// of the callable. The last exception is re-thrown if the transaction | ||
// was aborted because the number of callback attempts has been exceeded. | ||
try { | ||
$callbackResult = $callback($this); | ||
} catch (Throwable $throwable) { | ||
throw $throwable; | ||
} | ||
}; | ||
|
||
with_transaction($this->getSessionOrCreate(), $callbackFunction, $options); | ||
|
||
if ($attemptsLeft < 0 && $throwable) { | ||
throw $throwable; | ||
} | ||
|
||
return $callbackResult; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to use
--setParameter transactionLifetimeLimitSeconds=5
(or a lower value) to minimize deadlock time in CI.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.