Coder Social home page Coder Social logo

lock's Introduction

Requirements | Installation | Usage | License and authors | Donations

php-lock/lock

Latest Stable Version Total Downloads Latest Unstable Version Build Status License

This library helps executing critical code in concurrent situations.

php-lock/lock follows semantic versioning. Read more on semver.org.


Requirements

  • PHP 7.1 or above
  • Optionally nrk/predis to use the Predis locks.
  • Optionally the php-pcntl extension to enable locking with flock() without busy waiting in CLI scripts.
  • Optionally flock(), ext-redis, ext-pdo_mysql, ext-pdo_sqlite, ext-pdo_pgsql or ext-memcached can be used as a backend for locks. See examples below.
  • If ext-redis is used for locking and is configured to use igbinary for serialization or lzf for compression, additionally ext-igbinary and/or ext-lzf have to be installed.

Installation

Composer

To use this library through composer, run the following terminal command inside your repository's root folder.

composer require "malkusch/lock"

Usage

This library uses the namespace malkusch\lock.

Mutex

The malkusch\lock\mutex\Mutex class is an abstract class and provides the base API for this library.

Mutex::synchronized()

malkusch\lock\mutex\Mutex::synchronized() executes code exclusively. This method guarantees that the code is only executed by one process at once. Other processes have to wait until the mutex is available. The critical code may throw an exception, which would release the lock as well.

This method returns whatever is returned to the given callable. The return value is not checked, thus it is up to the user to decide if for example the return value false or null should be seen as a failed action.

Example:

$newBalance = $mutex->synchronized(function () use (
    $bankAccount,
    $amount
): int {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException('You have no credit.');
    }
    $bankAccount->setBalance($balance);

    return $balance;
});

Mutex::check()

malkusch\lock\mutex\Mutex::check() sets a callable, which will be executed when malkusch\lock\util\DoubleCheckedLocking::then() is called, and performs a double-checked locking pattern, where it's return value decides if the lock needs to be acquired and the synchronized code to be executed.

See https://en.wikipedia.org/wiki/Double-checked_locking for a more detailed explanation of that feature.

If the check's callable returns false, no lock will be acquired and the synchronized code will not be executed. In this case the malkusch\lock\util\DoubleCheckedLocking::then() method, will also return false to indicate that the check did not pass either before or after acquiring the lock.

In the case where the check's callable returns a value other than false, the malkusch\lock\util\DoubleCheckedLocking::then() method, will try to acquire the lock and on success will perform the check again. Only when the check returns something other than false a second time, the synchronized code callable, which has been passed to then() will be executed. In this case the return value of then() will be what ever the given callable returns and thus up to the user to return false or null to indicate a failed action as this return value will not be checked by the library.

Example:

$newBalance = $mutex->check(function () use ($bankAccount, $amount): bool {
    return $bankAccount->getBalance() >= $amount;
})->then(function () use ($bankAccount, $amount): int {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    $bankAccount->setBalance($balance);

    return $balance;
});

if (false === $newBalance) {
    if ($balance < 0) {
        throw new \DomainException('You have no credit.');
    }
}

Extracting code result after lock release exception

Mutex implementations based on malkush\lock\mutex\LockMutex will throw malkusch\lock\exception\LockReleaseException in case of lock release problem, but the synchronized code block will be already executed at this point. In order to read the code result (or an exception thrown there), LockReleaseException provides methods to extract it.

Example:

try {
    // or $mutex->check(...)
    $result = $mutex->synchronized(function () {
        if (someCondition()) {
            throw new \DomainException();
        }

        return "result";
    });
} catch (LockReleaseException $unlockException) {
    if ($unlockException->getCodeException() !== null) {
        $codeException = $unlockException->getCodeException()
        // do something with the code exception
    } else {
        $code_result = $unlockException->getCodeResult();
        // do something with the code result
    }

    // deal with LockReleaseException or propagate it
    throw $unlockException;
}

Implementations

Because the malkusch\lock\mutex\Mutex class is an abstract class, you can choose from one of the provided implementations or create/extend your own implementation.

CASMutex

The CASMutex has to be used with a Compare-and-swap operation. This mutex is lock free. It will repeat executing the code until the CAS operation was successful. The code should therefore notify the mutex by calling malkusch\lock\mutex\CASMutex::notify().

As the mutex keeps executing the critical code, it must not have any side effects as long as the CAS operation was not successful.

Example:

$mutex = new CASMutex();
$mutex->synchronized(function () use ($memcached, $mutex, $amount): void {
    $balance = $memcached->get("balance", null, $casToken);
    $balance -= $amount;
    if (!$memcached->cas($casToken, "balance", $balance)) {
        return;
    }
    $mutex->notify();
});

FlockMutex

The FlockMutex is a lock implementation based on flock().

Example:

$mutex = new FlockMutex(fopen(__FILE__, "r"));
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

Timeouts are supported as an optional second argument. This uses the ext-pcntl extension if possible or busy waiting if not.

MemcachedMutex

The MemcachedMutex is a spinlock implementation which uses the Memcached API.

Example:

$memcache = new \Memcached();
$memcache->addServer("localhost", 11211);

$mutex = new MemcachedMutex("balance", $memcache);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

PHPRedisMutex

The PHPRedisMutex is the distributed lock implementation of RedLock which uses the phpredis extension.

This implementation requires at least phpredis-2.2.4.

If used with a cluster of Redis servers, acquiring and releasing locks will continue to function as long as a majority of the servers still works.

Example:

$redis = new Redis();
$redis->connect("localhost");

$mutex = new PHPRedisMutex([$redis], "balance");
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

PredisMutex

The PredisMutex is the distributed lock implementation of RedLock which uses the Predis API.

Example:

$redis = new Client("redis://localhost");

$mutex = new PredisMutex([$redis], "balance");
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

SemaphoreMutex

The SemaphoreMutex is a lock implementation based on Semaphore.

Example:

$semaphore = sem_get(ftok(__FILE__, "a"));
$mutex = new SemaphoreMutex($semaphore);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

TransactionalMutex

The TransactionalMutex delegates the serialization to the DBS. The exclusive code is executed within a transaction. It's up to you to set the correct transaction isolation level. However if the transaction fails (i.e. a PDOException was thrown), the code will be executed again in a new transaction. Therefore the code must not have any side effects besides SQL statements. Also the isolation level should be conserved for the repeated transaction. If the code throws an exception, the transaction is rolled back and not replayed again.

Example:

$mutex = new TransactionalMutex($pdo);
$mutex->synchronized(function () use ($pdo, $accountId, $amount) {
    $select = $pdo->prepare(
        "SELECT balance FROM account WHERE id = ? FOR UPDATE"
    );
    $select->execute([$accountId]);
    $balance = $select->fetchColumn();

    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $pdo->prepare("UPDATE account SET balance = ? WHERE id = ?")
        ->execute([$balance, $accountId]);
});

MySQLMutex

The MySQLMutex uses MySQL's GET_LOCK function.

It supports time outs. If the connection to the database server is lost or interrupted, the lock is automatically released.

Note that before MySQL 5.7.5 you cannot use nested locks, any new lock will silently release already held locks. You should probably refrain from using this mutex on MySQL versions < 5.7.5.

$pdo = new PDO("mysql:host=localhost;dbname=test", "username");

$mutex = new MySQLMutex($pdo, "balance", 15);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

PgAdvisoryLockMutex

The PgAdvisoryLockMutex uses PostgreSQL's advisory locking functions.

Named locks are offered. PostgreSQL locking functions require integers but the conversion is handled automatically.

No time outs are supported. If the connection to the database server is lost or interrupted, the lock is automatically released.

$pdo = new PDO("pgsql:host=localhost;dbname=test;", "username");

$mutex = new PgAdvisoryLockMutex($pdo, "balance");
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");
    }
    $bankAccount->setBalance($balance);
});

License and authors

This project is free and under the WTFPL. Responsible for this project is Willem Stuursma-Ruwen [email protected].

Donations

If you like this project and feel generous donate a few Bitcoins here: 1P5FAZ4QhXCuwYPnLZdk3PJsqePbu1UDDA

lock's People

Contributors

athos-ribeiro avatar furgas avatar glensc avatar malkusch avatar mvorisek avatar staabm avatar thelevti avatar willemstuursma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lock's Issues

Check if the lock is taken

Hi! We encountered a situation when we would like to terminate all the calls when a locked call is already in execution (instead of waiting). @malkusch do you plan to extend the library with this feature in the near future? 👍

RedisMutex unnecessarily generating new tokens

I'm having an issue I can't reliably reproduce. It seems like the lock is allowing concurrent access but I'm not 100% sure.

I noticed the RedisMutex generates a new token each time it tries to acquire the lock.

Looking at Antirez's implementation it looks like it keeps retrying with the same token.

Generating a new token each time increases the likelihood of deleting the key belonging to another process, since you now have multiple tries to generate a collision instead of just one.

I think this issue combined with rand not being very random is causing collisions fairly often.

Rework redis locking

Some improvements that can be made:

  • If the lock fails to be acquired on a majority of servers, immediately abort the lock.
  • Only release locks on servers where we tried to set the lock
  • Further investigate multiplexing (does not seem possible with current approaches)
  • Acquire locks in random order of servers

Unit Test fails if I try to use parallel threads

The following PHPUnit test fails if I try to use parallel threads

I'm using php-lock version 2.1 on windows with php version 7.4.6

If I deactivate the $this->mutex->synchronized function by commenting out the function the test is executed without an error.
Does anyone have an idea whats wrong with my test case?

<?php
namespace Squeeze;

use Exception;
use malkusch\lock\mutex\FlockMutex;
use PHPUnit\Framework\TestCase;

class ThreadLockTest extends TestCase
{

    /**
     * @var FlockMutex
     */
    private $mutex;

    /**
     * @var resource
     */
    private $file;

    protected function setUp(): void
    {
        parent::setUp();

        $this->file = __DIR__ . DIRECTORY_SEPARATOR . 'flock';
        $this->mutex = new FlockMutex(fopen($this->file, 'w'), 1);
    }

    protected function tearDown(): void
    {
        unlink($this->file);

        parent::tearDown();
    }

    public function testThreadLock(): void
    {
        $syncFunction = function ($i) {
            try {
                require_once __DIR__ . '/../../../bootstrap/app.php';
                echo $i .' Running ' . PHP_EOL;
                return $this->mutex->synchronized(function () use ($i) : string {
                    sleep(1);
                    return $i .' Finished.' . PHP_EOL;
                });
            } catch (Exception $e) {
                return $e->getMessage() . PHP_EOL;
            }
        };

        $results = array();
        for ($i = 1; $i < 6; $i++) {
            $results[] = \parallel\run($syncFunction, array($i));
        }

        foreach ($results as $item) {
            echo $item->value();
        }
    }
}

SpinlockMutex unlock method

Shouldn't the unlock method of SpinlockMutex throw a TimeoutException if the executed time lasted longer than the set timeout instead of an LockReleaseException?

Related code from SpinlockMutex:

protected function unlock()
    {
        $elapsed = microtime(true) - $this->acquired;
        if ($elapsed >= $this->timeout) {
            $message = sprintf(
                "The code executed for %d seconds. But the timeout is %d seconds.",
                $elapsed,
                $this->timeout
            );
            throw new LockReleaseException($message);
        }
        /*
         * Worst case would still be one second before the key expires.
         * This guarantees that we don't delete a wrong key.
         */
        if (!$this->release($this->key)) {
            throw new LockReleaseException("Failed to release the lock.");
        }
    }

If not, can you explain why?

Loop::execute throws TimeoutException even though lock was acquired

(We are using php 7.3.22 on Linux together with php-lock v2.1, but it seems this issue persists in v2.2 as well)

I'm not quite sure if this is desired behavior, but I want to talk about Loop::execute():

Let's suppose the call $result = $code(); was successful, meaning $code() called Loop::end() and had side effects, but its successful execution took longer than $this->timeout.

In that case

if (microtime(true) >= $deadline) {
        throw TimeoutException::create($this->timeout);
}

which appears after the for loop, would still throw a TimeoutException, which seems counterintuitive to me.

A possible fix could be to only throw if additionally $this->looping is true:

if (microtime(true) >= $deadline && $this->looping) {
        throw TimeoutException::create($this->timeout);
}

See LongLockTest.txt for a quick unit test

Drop support for php 5.6 and 7.0

I think when php version 5.6 and 7.0 reach their end of life in the end of December of 2018 (see: http://php.net/supported-versions.php), this library should drop support for those versions in a new major version update. This will allow us to use all the new features that php 7.1+ bring.

In my fork I already started cleaning up the project, upgrading all composer packages to their latest version and also update tests and the travis ci config.

Any thoughts on this would be appreciated.

No access to code return value in case of ExecutionOutsideLockException or LockReleaseException

When LockReleaseException or ExecutionOutsideLockException is thrown there is no way to access the return value of the code block (it was executed).

Following change to LockMutex::synchronized() comes to mind:

    public function synchronized(callable $code)
    {
        $this->lock();

        $result = null;
        try {
            $result = $code();
        } finally {
	        try {
		        $this->unlock();
	        } catch (ExecutionOutsideLockException|LockReleaseException $e) {
		        $e->setCodeResult($result);
		        throw $e;
	        }
        }

        return $result;
    }

Also setCodeResult and getCodeResult should be added to both exception classes.

Of course a library user can always extend specific LockMutex subclass (ex. PredisMutex) and override the synchronized() method to deal with this problem, but maybe it's worth incorporating directly into the library.

MySQLMutex doesn't work correctly together with Sentry

After upgrading Symfony and Sentry ("sentry/sentry-symfony": "^4.3.0") I got the message that I needed to also upgrade doctrine/dbal ("doctrine/dbal": "^2.13.9",) , which then subsequently broke my use of MySQLMutex.

<b>Fatal error</b>:  Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 1 
passed to malkusch\lock\mutex\MySQLMutex::__construct() must be an instance of PDO, instance of 
Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingServerInfoAwareDriverConnection given, called in 
/var/www/html/src/ApiBundle/Controller/Invoicing/InvoiceController.php on line 533 in 
/var/www/html/vendor/malkusch/lock/classes/mutex/MySQLMutex.php:27

The MySQLMutex class only expects an instance of PDO to be provided, and in my case this is actually an instance of the Doctrine\DBAL\Connection interface with the updated Sentry package.

$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$pdo = $em->getConnection()->getWrappedConnection();
$mutex = new MySQLMutex($pdo, 'Some lock');

A solution would be to adapt the package so that the MySQLMutex class accepts both an instance of PDO and Doctrine\DBAL\Connection.

Might make a PR for this in the coming week.

half of redis server are not available!

What is this this error?

It's not possible to acquire a lock because at least half of the Redis serv er are not available.

This is my code:

        $redis = new Client;
        $redis->auth(REDIS-PASSWORD,null);
        $redis->connect();
        $my_range = range(1,100);

        foreach ($my_range as $each_range){
            $mutex = new PredisMutex([$redis], "balance");
            $mutex->synchronized(function () use($each_range) {
                $this->use_this_range($each_range);
            });
        }

Please add support for PHPUnit 11

I would like to update the Debian package to be compatible with PHPUnit 11 (in experimental branch currently): https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1070567

I have identified a few of the changes needed:

  • Add static to test providers.
    • Change $this-> to self::.
    • Need to find a replacement for getMockForAbstractClass. Maybe a callback that is called within the test case.
  • Change setMethods to onlyMethods or addMethods.

There could be additional changes needed, this is just what I found so far.

PHPRedisMutex add missing type handling

https://github.com/php-lock/lock/blob/master/classes/mutex/PHPRedisMutex.php#L55 does not handle a possible null return type. Base on the redis documentation (https://redis.io/commands/set), if the NX or XX option is specified, the return type can be NULL if the condition is not met. If null is returned, the method shall return false to comply with the return type.

1) SomeTest::testSomething
   TypeError: Return value of malkusch\lock\mutex\PHPRedisMutex::add() must be of the type bool, null returned
 /vendor/malkusch/lock/classes/mutex/PHPRedisMutex.php:48
 /vendor/malkusch/lock/classes/mutex/RedisMutex.php:82
 /vendor/malkusch/lock/classes/mutex/SpinlockMutex.php:72
 /vendor/malkusch/lock/classes/util/Loop.php:90
 /vendor/malkusch/lock/classes/mutex/SpinlockMutex.php:75
 /vendor/malkusch/lock/classes/mutex/LockMutex.php:37
 /vendor/malkusch/lock/classes/util/DoubleCheckedLocking.php:68

Numeric value out of range

php 8.1
PostgreSQL 12.9

SQLSTATE[22003]: Numeric value out of range: 7 ERROR: value "4132123129" is out of range for type integer

PHPRedisMutex not working

I have a simple piece of code:

$mutex = new \malkusch\lock\mutex\PHPRedisMutex([$app['redis']], "lock.".$guid, 8);
            $mutex->synchronized(function () use ($object) {
                echo 'test';
                sleep(3);
            });

but it throws the following exception:
LockReleaseException in SpinlockMutex.php line 94:
Failed to release the lock.

I am running PHP Version 7.0.15 with Redis extension 3.0.0

Seems that the problem is with the file PHPRedisMutex.php, at line 63:
return $redis->eval($script, $arguments, $numkeys);

At https://github.com/phpredis/phpredis#eval we can read:

Return value

Mixed. What is returned depends on what the LUA script itself returns, which could be a scalar value (int/string), or an array.

So, I have replaced the code at line 63 with:

$redis->eval($script, $arguments, $numkeys);
            return $redis->getlasterror() == 0;

In that way it seems to work properly. I think that with the new php redis extension the return of the eval function has changed, and becouse of that it returns an unexpected value. So I have changed the code in a way that it returns a boolean, checking if redis encountered an error.
I'm not sure if that's the best way to fix that trouble, but with my tests it seems working.

Nonblocking check for mutex

We are working with a queue system. If one mutex is already used, I would like it to be possible to put the job back on the queue. Like

Mutex::check(....)->then(If the mutex is free)->fail(If the mutex is not free)

Reconsider requiring ATTR_AUTOCOMMIT to be disabled (on MySQL)

Hi,

I just ran into the problem that the TransactionalMutex requires a PDO connection with ATTR_AUTOCOMMIT disabled.
As I see the code and consider the MySQL documentation on AUTOCOMMIT, it seems autocommit is already disabled when $this->pdo->beginTransaction(); is ran (this state lasts until the commit()).
Could you confirm whether my thought is correct? If so, would it be possible to skip the AUTOCOMMIT check -- at least for a MySQL PDO connection?

Thank you!

php 5.4

What features of PHP 5.5 do you use?
Is there any way to downgrade PHP version to 5.4 in composer.json?

DoubleCheckedLocking::then no return value

The \malkusch\lock\util\DoubleCheckedLocking::then method does return the return value of the \malkusch\lock\mutex\Mutex::synchronized method.

    /**
     * Executes a code only if a check is true.
     *
     * Both the check and the code execution are locked by a mutex.
     * Only if the check fails the method returns before acquiring a lock.
     *
     * @param callable $code The locked code.
     *
     * @throws \Exception The execution block or the check threw an exception.
     * @throws LockAcquireException The mutex could not be acquired.
     * @throws LockReleaseException The mutex could not be released.
     */
    public function then(callable $code)
    {
        if (!call_user_func($this->check)) {
            return;
        }
        $this->mutex->synchronized(function () use ($code) {
            if (call_user_func($this->check)) {
                call_user_func($code);
            }
        });
    }

I would expect that I can, in addition to pass values by reference in the use part of the callback, return something useful from the synchronized function call so that I can use that result afterwards. For example in a chained method call. Also, when the check fails, it should also return probably false? If the synchronized code will return false to indicate some sort of failure is up to the user. But, when using the ´check(callable)->then(callable)´, I can expect a return value of ´false´ so I know that the check failed.

flock with timeout support

I like FlockMutex because it work on kernel level and is reliable.

however there's the problem that acquiring the lock makes program wait forever. I wish to get error "unable to obtain lock withing timeout"

for flock there's LOCK_NB flag which can be used.

ps: SemaphoreMutex has similar issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.