One of the higher-level abstractions provided by the SDK are Waiters. Waiters help make it easier to work with
eventually consistent systems by providing an easy way to wait until a resource enters into a particular state by
polling the resource. You can find a list of the waiters supported by a client by viewing the API Documentation of a
service client. Any method with a name starting with "waitUntil
" will create and invoke a Waiter.
In the following example, the Amazon S3 Client is used to create a bucket. Then the Waiter is used to wait until the bucket exists.
// Create a bucket
$s3Client->createBucket(array('Bucket' => 'my-bucket'));
// Wait until the created bucket is available
$s3Client->waitUntil('BucketExists', array('Bucket' => 'my-bucket'));
If the Waiter has to poll the bucket too many times, it will throw an Aws\Common\Exception\RuntimeException
exception.
You can tune the number of polling attempts issued by a Waiter or the number of seconds to delay between each poll by passing optional values prefixed with "waiter.":
$s3Client->waitUntil('BucketExists', array(
'Bucket' => 'my-bucket',
'waiter.interval' => 10,
'waiter.max_attempts' => 3
));
To interact with the Waiter object directly, you must use the getWaiter()
method. The following code is equivalent
to the example in the preceding section.
$bucketExistsWaiter = $s3Client->getWaiter('BucketExists')
->setConfig(array('Bucket' => 'my-bucket'))
->setInterval(10)
->setMaxAttempts(3);
$bucketExistsWaiter->wait();
One benefit of working directly with the Waiter object is that you can attach event listeners. Waiters emit up to two events in each wait cycle. A wait cycle does the following:
waiter.before_attempt
event.max_attempts
is reached, an exception is thrown.waiter.before_wait
event.interval
amount of seconds.Waiter objects extend the Guzzle\Common\AbstractHasDispatcher
class which exposes the addSubscriber()
method and
getEventDispatcher()
method. To attach listeners, you can use the following example, which is a modified version of
the previous one.
// Get and configure the Waiter object
$waiter = $s3Client->getWaiter('BucketExists')
->setConfig(array('Bucket' => 'my-bucket'))
->setInterval(10)
->setMaxAttempts(3);
// Get the event dispatcher and register listeners for both events emitted by the Waiter
$dispatcher = $waiter->getEventDispatcher();
$dispatcher->addListener('waiter.before_attempt', function () {
echo "Checking if the wait condition has been met…\n";
});
$dispatcher->addListener('waiter.before_wait', function () use ($waiter) {
$interval = $waiter->getInterval();
echo "Sleeping for {$interval} seconds…\n";
});
$waiter->wait();
It is possible to implement custom Waiter objects if your use case requires application-specific Waiter logic or Waiters
that are not yet supported by the SDK. You can use the getWaiterFactory()
and setWaiterFactory()
methods on the
client to manipulate the Waiter factory used by the client such that your custom Waiter can be instantiated. By default
the service clients use a Aws\Common\Waiter\CompositeWaiterFactory
which allows you to add additional factories if
needed. The following example shows how to implement a contrived custom Waiter class and then modify a client's Waiter
factory such that it can create instances of the custom Waiter.
namespace MyApp\FakeWaiters
{
use Aws\Common\Waiter\AbstractResourceWaiter;
class SleptThreeTimes extends AbstractResourceWaiter
{
public function doWait()
{
if ($this->attempts < 3) {
echo "Need to sleep…\n";
return false;
} else {
echo "Now I've slept 3 times.\n";
return true;
}
}
}
}
namespace
{
use Aws\S3\S3Client;
use Aws\Common\Waiter\WaiterClassFactory;
$s3Client = S3Client::factory();
$compositeFactory = $s3Client->getWaiterFactory();
$compositeFactory->addFactory(new WaiterClassFactory('MyApp\FakeWaiters'));
$waiter = $s3Client->waitUntil('SleptThreeTimes');
}
The result of this code should look like the following:
Need to sleep…
Need to sleep…
Need to sleep…
Now I've slept 3 times.
The Waiters that are included in the SDK are defined in the service description for their client. They are defined using a configuration DSL (domain-specific language) that describes the default wait intervals, wait conditions, and how to check or poll the resource to resolve the condition.
This data is automatically consumed and used by the Aws\Common\Waiter\WaiterConfigFactory
class when a client is
instantiated so that the waiters defined in the service description are available to the client.
The following is an excerpt of the Amazon Glacier service description that defines the Waiters provided by
Aws\Glacier\GlacierClient
.
return array(
// ...
'waiters' => array(
'__default__' => array(
'interval' => 3,
'max_attempts' => 15,
),
'__VaultState' => array(
'operation' => 'DescribeVault',
),
'VaultExists' => array(
'extends' => '__VaultState',
'success.type' => 'output',
'description' => 'Wait until a vault can be accessed.',
'ignore_errors' => array(
'ResourceNotFoundException',
),
),
'VaultNotExists' => array(
'extends' => '__VaultState',
'description' => 'Wait until a vault is deleted.',
'success.type' => 'error',
'success.value' => 'ResourceNotFoundException',
),
),
// ...
);
In order for you to contribute Waiters to the SDK, you will need to implement them using the Waiters DSL. The DSL is not documented yet, since it is currently subject to change, so if you are interested in helping to implement more Waiters, please reach out to us via GitHub.