private async Task HandleEncodingJobEvent(EncodingJobEvent notification)
        {
            string jobId = notification.GetJobId();

            // Lookup the uploaded video's Id by job Id
            PreparedStatement lookupPrepared =
                await _statementCache.NoContext.GetOrAddAsync("SELECT videoid FROM uploaded_video_jobs_by_jobid WHERE jobid = ?");

            RowSet lookupRows = await _session.ExecuteAsync(lookupPrepared.Bind(jobId)).ConfigureAwait(false);

            Row lookupRow = lookupRows.SingleOrDefault();

            if (lookupRow == null)
            {
                throw new InvalidOperationException(string.Format("Could not find video for job id {0}. Were multiple jobs started for a video?", jobId));
            }

            var videoId = lookupRow.GetValue <Guid>("videoid");

            // Log the event to C* (this should be idempotent in case of dupliacte tries since we're keyed by the job id, date, and etag in C*)
            PreparedStatement preparedStatement = await _statementCache.NoContext.GetOrAddAsync(
                "INSERT INTO encoding_job_notifications (videoid, status_date, etag, jobId, newstate, oldstate) VALUES (?, ?, ?, ?, ?, ?) USING TIMESTAMP ?");

            string         newState   = notification.GetNewState();
            string         oldState   = notification.GetOldState();
            DateTimeOffset statusDate = notification.TimeStamp;

            // INSERT INTO encoding_job_notifications ...
            await _session.ExecuteAsync(
                preparedStatement.Bind(videoId, statusDate, notification.ETag, jobId, newState, oldState, statusDate.ToMicrosecondsSinceEpoch())).ConfigureAwait(false);

            // See if the job has finished and if not, just bail
            if (notification.IsJobFinished() == false)
            {
                return;
            }

            // Publish the appropriate event based on whether the job was successful or not
            if (notification.WasSuccessful())
            {
                await _bus.Publish(new UploadedVideoProcessingSucceeded
                {
                    VideoId   = videoId,
                    Timestamp = statusDate
                }).ConfigureAwait(false);

                return;
            }

            await _bus.Publish(new UploadedVideoProcessingFailed
            {
                VideoId   = videoId,
                Timestamp = statusDate
            }).ConfigureAwait(false);
        }
        private async Task HandleEncodingJobEvent(EncodingJobEvent notification)
        {
            string jobId = notification.GetJobId();

            // Lookup the uploaded video's Id by job Id
            PreparedStatement lookupPrepared =
                await _statementCache.NoContext.GetOrAddAsync("SELECT videoid FROM uploaded_video_jobs_by_jobid WHERE jobid = ?");
            RowSet lookupRows = await _session.ExecuteAsync(lookupPrepared.Bind(jobId)).ConfigureAwait(false);
            Row lookupRow = lookupRows.SingleOrDefault();
            if (lookupRow == null)
                throw new InvalidOperationException(string.Format("Could not find video for job id {0}. Were multiple jobs started for a video?", jobId));

            var videoId = lookupRow.GetValue<Guid>("videoid");

            // Log the event to C* (this should be idempotent in case of dupliacte tries since we're keyed by the job id, date, and etag in C*)
            PreparedStatement preparedStatement = await _statementCache.NoContext.GetOrAddAsync(
                "INSERT INTO encoding_job_notifications (videoid, status_date, etag, jobId, newstate, oldstate) VALUES (?, ?, ?, ?, ?, ?) USING TIMESTAMP ?");

            string newState = notification.GetNewState();
            string oldState = notification.GetOldState();
            DateTimeOffset statusDate = notification.TimeStamp;

            // INSERT INTO encoding_job_notifications ...
            await _session.ExecuteAsync(
                preparedStatement.Bind(videoId, statusDate, notification.ETag, jobId, newState, oldState, statusDate.ToMicrosecondsSinceEpoch())).ConfigureAwait(false);

            // See if the job has finished and if not, just bail
            if (notification.IsJobFinished() == false)
                return;

            // Publish the appropriate event based on whether the job was successful or not
            if (notification.WasSuccessful())
            {
                await _bus.Publish(new UploadedVideoProcessingSucceeded
                {
                    VideoId = videoId,
                    Timestamp = statusDate
                }).ConfigureAwait(false);
                return;
            }

            await _bus.Publish(new UploadedVideoProcessingFailed
            {
                VideoId = videoId,
                Timestamp = statusDate
            }).ConfigureAwait(false);
        }
        private async Task ExecuteImpl(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (_initialized == false)
            {
                await Initialize().ConfigureAwait(false);
            }

            bool gotSomeMessages;

            do
            {
                // Always start by assuming we won't get any messages from the queue
                gotSomeMessages = false;

                // Get a batch of messages
                IEnumerable <CloudQueueMessage> messages = await _queue.GetMessagesAsync(MessagesPerGet, cancellationToken).ConfigureAwait(false);

                foreach (CloudQueueMessage message in messages)
                {
                    // Check for cancellation before processing a message
                    cancellationToken.ThrowIfCancellationRequested();

                    // We obviously got some messages since we're processing one
                    gotSomeMessages = true;

                    // Try to deserialize the message to an EncodingJobEvent
                    EncodingJobEvent jobEvent = null;
                    try
                    {
                        var settings = new JsonSerializerSettings {
                            DateTimeZoneHandling = DateTimeZoneHandling.Utc
                        };
                        jobEvent = JsonConvert.DeserializeObject <EncodingJobEvent>(message.AsString, settings);
                    }
                    catch (Exception e)
                    {
                        Logger.Warn("Exception while deserializing event, message will be deleted.", e);
                    }

                    // If there was a problem with deserialization, just assume a poison message and delete it
                    if (jobEvent == null)
                    {
                        await _queue.DeleteMessageAsync(message, cancellationToken).ConfigureAwait(false);

                        continue;
                    }

                    // Ignore any messages that aren't for JobStateChanges
                    if (jobEvent.IsJobStateChangeEvent() == false)
                    {
                        await _queue.DeleteMessageAsync(message, cancellationToken).ConfigureAwait(false);

                        continue;
                    }

                    // Otherwise, handle the event
                    bool handledSuccessfully = false;
                    try
                    {
                        await HandleEncodingJobEvent(jobEvent).ConfigureAwait(false);

                        handledSuccessfully = true;
                    }
                    catch (Exception e)
                    {
                        Logger.Error("Error while handling JobStateChanged message", e);
                    }

                    // If the message was handled successfully, just delete it
                    if (handledSuccessfully)
                    {
                        await _queue.DeleteMessageAsync(message, cancellationToken).ConfigureAwait(false);

                        continue;
                    }

                    // If the message is over the number of retries, consider it a poison message, log it and delete it
                    if (jobEvent.RetryAttempts >= MaxRetries)
                    {
                        // Move the message to a poison queue (NOTE: because Add + Delete aren't "transactional" it is possible
                        // a poison message might get added more than once to the poison queue, but that's OK)
                        Logger.Fatal(string.Format("Giving up on message: {0}", message.AsString));
                        await _poisonQueue.AddMessageAsync(message, cancellationToken).ConfigureAwait(false);

                        await _queue.DeleteMessageAsync(message, cancellationToken).ConfigureAwait(false);

                        continue;
                    }

                    // Increment the retry attempts and then modify the message in place so it will be processed again
                    int secondsUntilRetry = (2 ^ jobEvent.RetryAttempts) * 10;
                    jobEvent.RetryAttempts++;
                    message.SetMessageContent(JsonConvert.SerializeObject(jobEvent));
                    await _queue.UpdateMessageAsync(message, TimeSpan.FromSeconds(secondsUntilRetry),
                                                    MessageUpdateFields.Content | MessageUpdateFields.Visibility, cancellationToken).ConfigureAwait(false);
                }

                // If we got some messages from the queue, keep processing until we don't get any
            } while (gotSomeMessages);

            // Exit method to allow a cooldown period (10s) between polling the queue whenever we run out of messages
        }