/// <summary>
        /// Converts a string from lowercase to uppercase, taking an extra long time.
        /// During that time, it periodically calls testCancel so that the caller can
        /// abort the operation if it's taking too long.
        /// </summary>
        /// <returns>An uppercase string.</returns>
        private string ShoutString(string text, ThrowIfAborted throwIfAborted)
        {
            string upperText = text.ToUpper();

            // Pretend like we're working hard and taking time.  Time is a function of
            // the number of letters in the word.
            var workDeadline = DateTime.UtcNow + TimeSpan.FromSeconds(
                upperText.Length * (int)Math.Log(upperText.Length));
            var oneSecond = TimeSpan.FromSeconds(1);

            while (workDeadline > DateTime.UtcNow)
            {
                throwIfAborted();
                System.Threading.Thread.Sleep(oneSecond);
            }

            // Simulate different kinds of errors depending on text.
            if (upperText.Contains("CHICKEN"))
            {
                // Simulate a fatal error that cannot possible be handled.
                throw new FatalException("Oh no!  Not chickens!");
            }
            if (upperText.Contains("CORN"))
            {
                // Simulate a flaky error that happens sometimes, but not always.
                if (_init.Random.Next(3) > 0)
                {
                    throw new CornException();
                }
            }
            if (upperText.Contains("COW"))
            {
                // Simulate an error that eventually times out with error.
                throw new CowException();
            }
            return(upperText);
        }
        /// <summary>
        /// Converts a string from lowercase to uppercase, taking an extra long time.
        /// During that time, it periodically calls testCancel so that the caller can
        /// abort the operation if it's taking too long.
        /// </summary>
        /// <returns>An uppercase string.</returns>
        private string ShoutString(string text, ThrowIfAborted throwIfAborted)
        {
            string upperText = text.ToUpper();

            // Pretend like we're working hard and taking time.  Time is a function of
            // the number of letters in the word.
            var workDeadline = DateTime.UtcNow + TimeSpan.FromSeconds(
                 upperText.Length * (int)Math.Log(upperText.Length));
            var oneSecond = TimeSpan.FromSeconds(1);
            while (workDeadline > DateTime.UtcNow)
            {
                throwIfAborted();
                System.Threading.Thread.Sleep(oneSecond);
            }

            // Simulate different kinds of errors depending on text.
            if (upperText.Contains("CHICKEN"))
            {
                // Simulate a fatal error that cannot possible be handled.
                throw new FatalException("Oh no!  Not chickens!");
            }
            if (upperText.Contains("CORN"))
            {
                // Simulate a flaky error that happens sometimes, but not always.
                if (_init.Random.Next(3) > 0)
                    throw new CornException();
            }
            if (upperText.Contains("COW"))
            {
                // Simulate an error that eventually times out with error.
                throw new CowException();
            }
            return upperText;
        }
        /// <summary>
        /// Waits for a shout request message to arrive in the Pub/Sub
        /// subscription.  Converts the text to uppercase and posts the results
        /// back to the website.
        /// </summary>
        /// <returns>
        /// The number of messages pulled from the subscription,
        /// or -1 if an expected error occurred.
        /// </returns>
        public int ShoutOrThrow(System.Threading.CancellationToken cancellationToken)
        {
            // Pull a shout request message from the subscription.
            string subscriptionPath = MakeSubscriptionPath(_init.SubscriptionName);

            WriteLog("Pulling shout request messages from " + subscriptionPath + "...",
                     TraceEventType.Verbose);
            var pullRequest = _init.PubsubService.Projects.Subscriptions.Pull(
                new PullRequest()
            {
                MaxMessages       = 1,
                ReturnImmediately = false
            }, subscriptionPath).ExecuteAsync();

            Task.WaitAny(new Task[] { pullRequest }, cancellationToken);
            var pullResponse = pullRequest.Result;

            int messageCount = pullResponse.ReceivedMessages == null ? 0
                : pullResponse.ReceivedMessages.Count;

            WriteLog("Received " + messageCount + " messages.",
                     TraceEventType.Information);
            if (messageCount < 1)
            {
                return(0);  // Nothing pulled.  Nothing to do.
            }
            // Examine the received message.
            var      shoutRequestMessage = pullResponse.ReceivedMessages[0];
            var      attributes          = shoutRequestMessage.Message.Attributes;
            string   postStatusUrl;
            string   postStatusToken;
            DateTime requestDeadline;

            try
            {
                postStatusUrl   = attributes["postStatusUrl"];
                postStatusToken = attributes["postStatusToken"];
                long unixDeadline = Convert.ToInt64(attributes["deadline"]);
                requestDeadline = FromUnixTime(unixDeadline);
            }
            catch (Exception e)
            {
                WriteLog("Bad shout request message attributes.\n" + e.ToString(),
                         TraceEventType.Warning);
                Acknowledge(shoutRequestMessage.AckId);
                return(-1);
            }

            // Tell the world we are shouting this request.
            PublishStatus(postStatusUrl, postStatusToken, "shouting");
            WriteLog("Shouting " + postStatusUrl, TraceEventType.Verbose);

            try
            {
                // Decode the payload, the string we want to shout.
                byte[] data          = Convert.FromBase64String(shoutRequestMessage.Message.Data);
                string decodedString = Encoding.UTF8.GetString(data);

                // Watch the clock and cancellation token as we work.  We need to extend the
                // ack deadline if the request takes a while.
                var            tenSeconds     = TimeSpan.FromSeconds(10);
                DateTime       ackDeadline    = DateTime.UtcNow + tenSeconds;
                ThrowIfAborted throwIfAborted = () =>
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    var now = DateTime.UtcNow;
                    if (requestDeadline < now)
                    {
                        throw new FatalException("Request timed out.");
                    }
                    if (ackDeadline < now)
                    {
                        // Tell the subscription we need more time:
                        WriteLog("Need more time...", TraceEventType.Verbose);
                        _init.PubsubService.Projects.Subscriptions.ModifyAckDeadline(
                            new ModifyAckDeadlineRequest
                        {
                            AckIds             = new string[] { shoutRequestMessage.AckId },
                            AckDeadlineSeconds = 15,
                        }, MakeSubscriptionPath(_init.SubscriptionName)).Execute();
                        ackDeadline = now + tenSeconds;
                    }
                };

                // Shout it.
                string upperText = ShoutString(decodedString, throwIfAborted);

                // Publish the result.
                PublishStatus(postStatusUrl, postStatusToken, "success", upperText);
                Acknowledge(shoutRequestMessage.AckId);
                return(1);
            }
            catch (OperationCanceledException)
            {
                return(1);  // Service stopped.  Nothing to report.
            }
            catch (FatalException e)
            {
                WriteLog("Fatal exception while shouting:\n" + e.Message, TraceEventType.Error);
                Acknowledge(shoutRequestMessage.AckId);
                PublishStatus(postStatusUrl, postStatusToken, "fatal", e.Message);
                return(-1);
            }
            catch (Exception e)
            {
                // Something went wrong while shouting.  Report the error.
                WriteLog("Exception while shouting:\n" + e.Message, TraceEventType.Error);
                PublishStatus(postStatusUrl, postStatusToken, "error", e.Message);
                return(-1);
            }
        }