Context:
/*
 * @author Valentin Simonov / http://va.lent.in/
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TouchScript.Hit;
using TouchScript.Layers;
using TouchScript.Utils;
using TouchScript.Utils.Attributes;
using UnityEngine;

namespace TouchScript.Gestures
{
    /// <summary>
    /// Base class for all gestures
    /// </summary>
    public abstract class Gesture : MonoBehaviour
    {
        #region Constants

        /// <summary>
        /// Message sent when gesture changes state if SendMessage is used.
        /// </summary>
        public const string STATE_CHANGE_MESSAGE = "OnGestureStateChange";

        /// <summary>
        /// Possible states of a gesture.
        /// </summary>
        public enum GestureState
        {
            /// <summary>
            /// Gesture is possible.
            /// </summary>
            Possible,

            /// <summary>
            /// Continuous gesture has just begun.
            /// </summary>
            Began,

            /// <summary>
            /// Started continuous gesture is updated.
            /// </summary>
            Changed,

            /// <summary>
            /// Continuous gesture is ended.
            /// </summary>
            Ended,

            /// <summary>
            /// Gesture is cancelled.
            /// </summary>
            Cancelled,

            /// <summary>
            /// Gesture is failed by itself or by another recognized gesture.
            /// </summary>
            Failed,

            /// <summary>
            /// Gesture is recognized.
            /// </summary>
            Recognized = Ended
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when gesture changes state.
        /// </summary>
        public event EventHandler<GestureStateChangeEventArgs> StateChanged
        {
            add { stateChangedInvoker += value; }
            remove { stateChangedInvoker -= value; }
        }

        // Needed to overcome iOS AOT limitations
        private EventHandler<GestureStateChangeEventArgs> stateChangedInvoker;

        #endregion

        #region Public properties

        /// <summary>
        /// Gets or sets another gesture which must fail before this gesture can be recognized.
        /// </summary>
        /// <value>
        /// The gesture which must fail before this gesture can be recognized;
        /// </value>
        public Gesture RequireGestureToFail
        {
            get { return requireGestureToFail; }
            set
            {
                if (requireGestureToFail != null) requireGestureToFail.StateChanged -= requiredToFailGestureStateChangedHandler;
                requireGestureToFail = value;
                if (requireGestureToFail != null) requireGestureToFail.StateChanged += requiredToFailGestureStateChangedHandler;
            }
        }

        /// <summary>
        /// Gets or sets the flag if touches should be treated as a cluster.
        /// </summary>
        /// <value><c>true</c> if touches should be treated as a cluster; otherwise, <c>false</c>.</value>
        /// <remarks>
        /// At the end of a gesture when touches are lifted off due to the fact that computers are faster than humans the very last touch's position will be gesture's <see cref="ScreenPosition"/> after that. This flag is used to combine several touches which from the point of a user were lifted off simultaneously and set their centroid as gesture's <see cref="ScreenPosition"/>.
        /// </remarks>
        public bool CombineTouches
        {
            get { return combineTouches; }
            set { combineTouches = value; }
        }

        /// <summary>
        /// Gets or sets time interval before gesture is recognized to combine all lifted touch points into a cluster to use its center as <see cref="ScreenPosition"/>.
        /// </summary>
        /// <value>Time in seconds to treat touches lifted off during this interval as a single gesture.</value>
        public float CombineTouchesInterval
        {
            get { return combineTouchesInterval; }
            set { combineTouchesInterval = value; }
        }

        /// <summary>
        /// Gets or sets whether gesture should use Unity's SendMessage in addition to C# events.
        /// </summary>
        /// <value><c>true</c> if gesture uses SendMessage; otherwise, <c>false</c>.</value>
        public bool UseSendMessage
        {
            get { return useSendMessage; }
            set { useSendMessage = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether state change events are broadcasted if <see cref="UseSendMessage"/> is true..
        /// </summary>
        /// <value><c>true</c> if state change events should be broadcaster; otherwise, <c>false</c>.</value>
        public bool SendStateChangeMessages
        {
            get { return sendStateChangeMessages; }
            set { sendStateChangeMessages = value; }
        }

        /// <summary>
        /// Gets or sets the target of Unity messages sent from this gesture.
        /// </summary>
        /// <value>The target of Unity messages.</value>
        public GameObject SendMessageTarget
        {
            get { return sendMessageTarget; }
            set
            {
                sendMessageTarget = value;
                if (value == null) sendMessageTarget = gameObject;
            }
        }

        /// <summary>
        /// Gets current gesture state.
        /// </summary>
        /// <value>Current state of the gesture.</value>
        public GestureState State
        {
            get { return state; }
            private set
            {
                PreviousState = state;
                state = value;

                switch (value)
                {
                    case GestureState.Possible:
                        onPossible();
                        break;
                    case GestureState.Began:
                        onBegan();
                        break;
                    case GestureState.Changed:
                        onChanged();
                        break;
                    case GestureState.Recognized:
                        onRecognized();
                        break;
                    case GestureState.Failed:
                        onFailed();
                        break;
                    case GestureState.Cancelled:
                        onCancelled();
                        break;
                }

                stateChangedInvoker.InvokeHandleExceptions(this, new GestureStateChangeEventArgs(state, PreviousState));
                if (useSendMessage && sendStateChangeMessages && SendMessageTarget != null) sendMessageTarget.SendMessage(STATE_CHANGE_MESSAGE, this, SendMessageOptions.DontRequireReceiver);
            }
        }

        /// <summary>
        /// Gets previous gesture state.
        /// </summary>
        /// <value>Previous state of the gesture.</value>
        public GestureState PreviousState { get; private set; }

        /// <summary>
        /// Gets current screen position.
        /// </summary>
        /// <value>Gesture's position in screen coordinates.</value>
        public virtual Vector2 ScreenPosition
        {
            get
            {
                if (activeTouches.Count == 0)
                {
                    if (!TouchManager.IsInvalidPosition(cachedScreenPosition)) return cachedScreenPosition;
                    return TouchManager.INVALID_POSITION;
                }
                return ClusterUtils.Get2DCenterPosition(activeTouches);
            }
        }

        /// <summary>
        /// Gets previous screen position.
        /// </summary>
        /// <value>Gesture's previous position in screen coordinates.</value>
        public virtual Vector2 PreviousScreenPosition
        {
            get
            {
                if (activeTouches.Count == 0)
                {
                    if (!TouchManager.IsInvalidPosition(cachedPreviousScreenPosition)) return cachedPreviousScreenPosition;
                    return TouchManager.INVALID_POSITION;
                }
                return ClusterUtils.GetPrevious2DCenterPosition(activeTouches);
            }
        }

        /// <summary>
        /// Gets normalized screen position.
        /// </summary>
        /// <value>Gesture's position in normalized screen coordinates.</value>
        public Vector2 NormalizedScreenPosition
        {
            get
            {
                var position = ScreenPosition;
                if (TouchManager.IsInvalidPosition(position)) return TouchManager.INVALID_POSITION;
                return new Vector2(position.x / Screen.width, position.y / Screen.height);
            }
        }

        /// <summary>
        /// Gets previous screen position.
        /// </summary>
        /// <value>Gesture's previous position in normalized screen coordinates.</value>
        public Vector2 PreviousNormalizedScreenPosition
        {
            get
            {
                var position = PreviousScreenPosition;
                if (TouchManager.IsInvalidPosition(position)) return TouchManager.INVALID_POSITION;
                return new Vector2(position.x / Screen.width, position.y / Screen.height);
            }
        }

        /// <summary>
        /// Gets list of gesture's active touch points.
        /// </summary>
        /// <value>The list of touches owned by this gesture.</value>
        public IList<ITouch> ActiveTouches
        {
            get { return new ReadOnlyCollection<ITouch>(activeTouches); }
        }

        /// <summary>
        /// An object implementing <see cref="IGestureDelegate"/> to be asked for gesture specific actions.
        /// </summary>
        public IGestureDelegate Delegate { get; set; }

        #endregion

        #region Private variables

        /// <summary>
        /// Reference to global GestureManager.
        /// </summary>
        protected IGestureManager gestureManager
        {
            // implemented as a property because it returns IGestureManager but we need to reference GestureManagerInstance to access internal methods
            get { return gestureManagerInstance; }
        }

        /// <summary>
        /// Reference to global TouchManager.
        /// </summary>
        protected ITouchManager touchManager { get; private set; }

        /// <summary>
        /// Touch points the gesture currently owns and works with.
        /// </summary>
        protected List<ITouch> activeTouches = new List<ITouch>();

        /// <summary>
        /// Cached transform of the parent object.
        /// </summary>
        protected Transform cachedTransform;

#pragma warning disable 0169
        [SerializeField]
        private bool advancedProps; // is used to save if advanced properties are opened or closed
#pragma warning restore 0169

        [SerializeField]
        [ToggleLeft]
        private bool combineTouches = false;

        [SerializeField]
        private float combineTouchesInterval = .3f;

        [SerializeField]
        [ToggleLeft]
        private bool useSendMessage = false;

        [SerializeField]
        [ToggleLeft]
        private bool sendStateChangeMessages = false;

        [SerializeField]
        private GameObject sendMessageTarget;

        [SerializeField]
        [NullToggle]
        private Gesture requireGestureToFail;

        [SerializeField]
        // Serialized list of gestures for Unity IDE.
        private List<Gesture> friendlyGestures = new List<Gesture>();

        // List of gestures for realtime.
        private List<int> friendlyGestureIds = new List<int>();

        private TimedSequence<ITouch> touchSequence = new TimedSequence<ITouch>();
        private GestureManagerInstance gestureManagerInstance;
        private GestureState delayedStateChange = GestureState.Possible;
        private bool requiredGestureFailed = false;
        private GestureState state = GestureState.Possible;

        /// <summary>
        /// Cached screen position.
        /// Used to keep tap's position which can't be calculated from touch points when the gesture is recognized since all touch points are gone.
        /// </summary>
        private Vector2 cachedScreenPosition;

        /// <summary>
        /// Cached previous screen position.
        /// Used to keep tap's position which can't be calculated from touch points when the gesture is recognized since all touch points are gone.
        /// </summary>
        private Vector2 cachedPreviousScreenPosition;

        #endregion

        #region Public methods

        /// <summary>
        /// Adds a friendly gesture.
        /// </summary>
        /// <param name="gesture">The gesture.</param>
        public virtual void AddFriendlyGesture(Gesture gesture)
        {
            if (gesture == null || gesture == this) return;

            registerFriendlyGesture(gesture);
            gesture.registerFriendlyGesture(this);
        }

        /// <summary>
        /// Checks if a gesture is friendly with this gesture.
        /// </summary>
        /// <param name="gesture">A gesture to check.</param>
        /// <returns>True if gestures are friendly; false otherwise.</returns>
        public bool IsFriendly(Gesture gesture)
        {
            return friendlyGestureIds.Contains(gesture.GetInstanceID());
        }

        /// <summary>
        /// Gets result of casting a ray from gesture touch points' centroid screen position.
        /// </summary>
        /// <returns>true if ray hits gesture's target; otherwise, false.</returns>
        public virtual bool GetTargetHitResult()
        {
            ITouchHit hit;
            return GetTargetHitResult(ScreenPosition, out hit);
        }

        /// <summary>
        /// Gets result of casting a ray from gesture touch points centroid screen position.
        /// </summary>
        /// <param name="hit">Raycast result</param>
        /// <returns>true if ray hits gesture's target; otherwise, false.</returns>
        public virtual bool GetTargetHitResult(out ITouchHit hit)
        {
            return GetTargetHitResult(ScreenPosition, out hit);
        }

        /// <summary>
        /// Gets result of casting a ray from specific screen position.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <returns>true if ray hits gesture's target; otherwise, false.</returns>
        public virtual bool GetTargetHitResult(Vector2 position)
        {
            ITouchHit hit;
            return GetTargetHitResult(position, out hit);
        }

        /// <summary>
        /// Gets result of casting a ray from specific screen position.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="hit">Raycast result.</param>
        /// <returns>true if ray hits gesture's target; otherwise, false.</returns>
        public virtual bool GetTargetHitResult(Vector2 position, out ITouchHit hit)
        {
            TouchLayer layer = null;
            if (!touchManager.GetHitTarget(position, out hit, out layer)) return false;

            if (cachedTransform == hit.Transform || hit.Transform.IsChildOf(cachedTransform)) return true;
            return false;
        }

        /// <summary>
        /// Determines whether gesture controls a touch point.
        /// </summary>
        /// <param name="touch">The touch.</param>
        /// <returns>
        ///   <c>true</c> if gesture controls the touch point; otherwise, <c>false</c>.
        /// </returns>
        public bool HasTouch(ITouch touch)
        {
            return activeTouches.Contains(touch);
        }

        /// <summary>
        /// Determines whether this instance can prevent the specified gesture.
        /// </summary>
        /// <param name="gesture">The gesture.</param>
        /// <returns>
        ///   <c>true</c> if this instance can prevent the specified gesture; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanPreventGesture(Gesture gesture)
        {
            if (Delegate == null)
            {
                if (gesture.CanBePreventedByGesture(this)) return !IsFriendly(gesture);
                return false;
            }
            return !Delegate.ShouldRecognizeSimultaneously(this, gesture);
        }

        /// <summary>
        /// Determines whether this instance can be prevented by specified gesture.
        /// </summary>
        /// <param name="gesture">The gesture.</param>
        /// <returns>
        ///   <c>true</c> if this instance can be prevented by specified gesture; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanBePreventedByGesture(Gesture gesture)
        {
            if (Delegate == null) return !IsFriendly(gesture);
            return !Delegate.ShouldRecognizeSimultaneously(this, gesture);
        }

        /// <summary>
        /// Specifies if gesture can receive this specific touch point.
        /// </summary>
        /// <param name="touch">The touch.</param>
        /// <returns><c>true</c> if this touch should be received by the gesture; otherwise, <c>false</c>.</returns>
        public virtual bool ShouldReceiveTouch(ITouch touch)
        {
            if (Delegate == null) return true;
            return Delegate.ShouldReceiveTouch(this, touch);
        }

        /// <summary>
        /// Specifies if gesture can begin or recognize.
        /// </summary>
        /// <returns><c>true</c> if gesture should begin; otherwise, <c>false</c>.</returns>
        public virtual bool ShouldBegin()
        {
            if (Delegate == null) return true;
            return Delegate.ShouldBegin(this);
        }

        #endregion

        #region Unity methods

        /// <inheritdoc />
        protected virtual void Awake()
        {
            cachedTransform = GetComponent<Transform>();

            foreach (var gesture in friendlyGestures)
            {
                AddFriendlyGesture(gesture);
            }
            RequireGestureToFail = requireGestureToFail;
        }

        /// <summary>
        /// Unity3d Start handler.
        /// </summary>
        protected virtual void OnEnable()
        {
            // TouchManager might be different in another scene
            touchManager = TouchManager.Instance;
            gestureManagerInstance = GestureManager.Instance as GestureManagerInstance;

            if (touchManager == null) Debug.LogError("No TouchManager found! Please add an instance of TouchManager to the scene!");
            if (gestureManagerInstance == null) Debug.LogError("No GesturehManager found! Please add an instance of GesturehManager to the scene!");

            if (sendMessageTarget == null) sendMessageTarget = gameObject;
            Reset();
        }

        /// <summary>
        /// Unity3d OnDisable handler.
        /// </summary>
        protected virtual void OnDisable()
        {
            setState(GestureState.Failed);
        }

        /// <summary>
        /// Unity3d OnDestroy handler.
        /// </summary>
        protected virtual void OnDestroy()
        {
            var copy = new List<Gesture>(friendlyGestures);
            foreach (var gesture in copy)
            {
                RemoveFriendlyGesture(gesture);
            }
            RequireGestureToFail = null;
        }

        #endregion

        #region Internal functions

        internal void SetState(GestureState value)
        {
            setState(value);
        }

        internal void Reset()
        {
            activeTouches.Clear();
            delayedStateChange = GestureState.Possible;
            requiredGestureFailed = false;
            reset();
        }

        internal void TouchesBegan(IList<ITouch> touches)
        {
            activeTouches.AddRange(touches);
            touchesBegan(touches);
        }

        internal void TouchesMoved(IList<ITouch> touches)
        {
            touchesMoved(touches);
        }

        internal void TouchesEnded(IList<ITouch> touches)
        {
            for (var i = 0; i < touches.Count; i++) activeTouches.Remove(touches[i]);
            touchesEnded(touches);
        }

        internal void TouchesCancelled(IList<ITouch> touches)
        {
            for (var i = 0; i < touches.Count; i++) activeTouches.Remove(touches[i]);
            touchesCancelled(touches);
        }

        internal virtual void RemoveFriendlyGesture(Gesture gesture)
        {
            if (gesture == null || gesture == this) return;

            unregisterFriendlyGesture(gesture);
            gesture.unregisterFriendlyGesture(this);
        }

        #endregion

        #region Protected methods

        /// <summary>
        /// Should the gesture cache this touch to use it later in calculation of <see cref="ScreenPosition"/>.
        /// </summary>
        /// <param name="value">Touch to cache.</param>
        /// <returns><c>true</c> if touch should be cached; <c>false</c> otherwise.</returns>
        protected virtual bool shouldCacheTouchPosition(ITouch value)
        {
            return true;
        }

        /// <summary>
        /// Tries to change gesture state.
        /// </summary>
        /// <param name="value">New state.</param>
        /// <returns><c>true</c> if state was changed; otherwise, <c>false</c>.</returns>
        protected bool setState(GestureState value)
        {
            if (gestureManagerInstance == null) return false;
            if (requireGestureToFail != null)
            {
                switch (value)
                {
                    case GestureState.Recognized:
                    case GestureState.Began:
                        if (!requiredGestureFailed)
                        {
                            delayedStateChange = value;
                            return false;
                        }
                        break;
                    case GestureState.Possible:
                    case GestureState.Failed:
                    case GestureState.Cancelled:
                        delayedStateChange = GestureState.Possible;
                        break;
                }
            }

            var newState = gestureManagerInstance.GestureChangeState(this, value);
            State = newState;

            return value == newState;
        }

        #endregion

        #region Callbacks

        /// <summary>
        /// Called when new touches appear.
        /// </summary>
        /// <param name="touches">The touches.</param>
        protected virtual void touchesBegan(IList<ITouch> touches) {}

        /// <summary>
        /// Called for moved touches.
        /// </summary>
        /// <param name="touches">The touches.</param>
        protected virtual void touchesMoved(IList<ITouch> touches) {}

        /// <summary>
        /// Called if touches are removed.
        /// </summary>
        /// <param name="touches">The touches.</param>
        protected virtual void touchesEnded(IList<ITouch> touches)
        {
            if (combineTouches)
            {
                foreach (var touch in touches)
                {
                    touchSequence.Add(touch);
                }

                if (activeTouches.Count == 0)
                {
                    // Checking which points were removed in clusterExistenceTime seconds to set their centroid as cached screen position
                    var cluster = touchSequence.FindElementsLaterThan(Time.time - combineTouchesInterval, shouldCacheTouchPosition);
                    cachedScreenPosition = ClusterUtils.Get2DCenterPosition(cluster);
                    cachedPreviousScreenPosition = ClusterUtils.GetPrevious2DCenterPosition(cluster);
                }
            }
            else
            {
                if (activeTouches.Count == 0)
                {
                    var lastPoint = touches[touches.Count - 1];
                    if (shouldCacheTouchPosition(lastPoint))
                    {
                        cachedScreenPosition = lastPoint.Position;
                        cachedPreviousScreenPosition = lastPoint.PreviousPosition;
                    }
                    else
                    {
                        cachedScreenPosition = TouchManager.INVALID_POSITION;
                        cachedPreviousScreenPosition = TouchManager.INVALID_POSITION;
                    }
                }
            }
        }

        /// <summary>
        /// Called when touches are cancelled.
        /// </summary>
        /// <param name="touches">The touches.</param>
        protected virtual void touchesCancelled(IList<ITouch> touches) {}

        /// <summary>
        /// Called to reset gesture state after it fails or recognizes.
        /// </summary>
        protected virtual void reset()
        {

Answer: cachedScreenPosition = TouchManager.INVALID_POSITION;

<think>
In the internal Reset() method, after clearing activeTouches and resetting delayedStateChange and requiredGestureFailed, it calls the protected virtual reset() to finish resetting the gesture. The typical next operation is to clear cached screen positions, setting them to the invalid sentinel value from TouchManager. Therefore the first line inside reset() should assign TouchManager.INVALID_POSITION to cachedScreenPosition.
</think>
cachedScreenPosition = TouchManager.INVALID_POSITION;


Context:
/*
Copyright 2010 Google Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using Google.Apis.Authentication;
using Google.Apis.Discovery;
using Google.Apis.Logging;
using Google.Apis.Testing;
using Google.Apis.Util;

#if !SILVERLIGHT
using System.IO.Compression;
#endif

namespace Google.Apis.Requests
{
    /// <summary>
    /// Request to a service.
    /// </summary>
    /// <remarks>
    /// Features which are not (yet) supported on SilverLight:
    /// - The UserAgent header.
    /// - GZip Compression
    /// </remarks>
    public class Request : IRequest
    {
        private const string UserAgent = "{0} google-api-dotnet-client/{1} {2}/{3}";
        private const string GZipUserAgentSuffix = " (gzip)";
        private const string GZipEncoding = "gzip";

        /// <summary>
        /// The charset used for content encoding.
        /// </summary>
        public static readonly Encoding ContentCharset = Encoding.UTF8;

        private static readonly ILogger logger = ApplicationContext.Logger.ForType<Request>();

        public const string DELETE = "DELETE";
        public const string GET = "GET";
        public const string PATCH = "PATCH";
        public const string POST = "POST";
        public const string PUT = "PUT";

        /// <summary>
        /// Defines the maximum number of retries which should be made if a request fails.
        /// </summary>
        public int MaximumRetries { get; set; }

        /// <summary>
        /// Defines the factor by which the waiting time is multiplied between retry attempts.
        /// </summary>
        public double RetryWaitTimeIncreaseFactor { get; set; }

        /// <summary>
        /// Defines the initial waiting time, which is used once the first retry has failed.
        /// </summary>
        public int RetryInitialWaitTime { get; set; }

        private static readonly String ApiVersion = Utilities.GetLibraryVersion();

        public static readonly ReadOnlyCollection<string> SupportedHttpMethods =
            new List<string> { POST, PUT, DELETE, GET, PATCH }.AsReadOnly();

        private string applicationName;

        public Request()
        {
            applicationName = Utilities.GetAssemblyTitle() ?? "Unknown_Application";
            Authenticator = new NullAuthenticator();
            WebRequestFactory = new HttpRequestFactory();
            Parameters = new ParameterCollection();

            MaximumRetries = 3;
            RetryWaitTimeIncreaseFactor = 2.0;
            RetryInitialWaitTime = 1000;
        }

        /// <summary>
        /// The authenticator used for this request.
        /// </summary>
        internal IAuthenticator Authenticator { get; private set; }

        /// <summary>
        /// The developer API Key sent along with the request.
        /// </summary>
        internal String DeveloperKey { get; private set; }

        /// <summary>
        /// Set of method parameters.
        /// </summary>
        [VisibleForTestOnly]
        internal ParameterCollection Parameters { get; set; }

        /// <summary>
        /// The application name used within the user agent string.
        /// </summary>
        [VisibleForTestOnly]
        internal string ApplicationName
        {
            get { return applicationName; }
        }

        private IService Service { get; set; }
        private IMethod Method { get; set; }
        private Uri BaseURI { get; set; }
        private string RPCName { get; set; } // note: this property is apparently never used
        private string Body { get; set; }
        private ReturnType ReturnType { get; set; }
        private ETagAction ETagAction { get; set; }
        private string ETag { get; set; }
        private string FieldsMask { get; set; }
        private string UserIp { get; set; }
        private string QuotaUser { get; set; }

        /// <summary>
        /// Defines whether this request can be sent multiple times.
        /// </summary>
        public bool SupportsRetry
        {
            get { return true; }
        }

        #region IRequest Members

        /// <summary>
        /// The method to call
        /// </summary>
        /// <returns>
        /// A <see cref="Request"/>
        /// </returns>
        public IRequest On(string rpcName)
        {
            RPCName = rpcName;

            return this;
        }

        /// <summary>
        /// Sets the type of data that is expected to be returned from the request.
        ///
        /// Defaults to Json.
        /// </summary>
        /// <param name="returnType">
        /// A <see cref="ReturnType"/>
        /// </param>
        /// <returns>
        /// A <see cref="Request"/>
        /// </returns>
        public IRequest Returning(ReturnType returnType)
        {
            ReturnType = returnType;
            return this;
        }

        /// <summary>
        /// Adds the parameters to the request.
        /// </summary>
        /// <returns>
        /// A <see cref="Request"/>
        /// </returns>
        public IRequest WithParameters(IDictionary<string, object> parameters)
        {
            Parameters = ParameterCollection.FromDictionary(parameters);
            return this;
        }

        /// <summary>
        /// Adds the parameters to the request.
        /// </summary>
        /// <returns>
        /// A <see cref="Request"/>
        /// </returns>
        public IRequest WithParameters(IEnumerable<KeyValuePair<string, string>> parameters)
        {
            Parameters = new ParameterCollection(parameters);
            return this;
        }

        /// <summary>
        /// Parses the specified querystring and adds these parameters to the request
        /// </summary>
        public IRequest WithParameters(string parameters)
        {
            Parameters = ParameterCollection.FromQueryString(parameters);
            return this;
        }

        /// <summary>
        /// Uses the string provied as the body of the request.
        /// </summary>
        public IRequest WithBody(string body)
        {
            Body = body;
            return this;
        }

        /// <summary>
        /// Uses the provided authenticator to add authentication information to this request.
        /// </summary>
        public IRequest WithAuthentication(IAuthenticator authenticator)
        {
            authenticator.ThrowIfNull("Authenticator");
            Authenticator = authenticator;
            return this;
        }

        /// <summary>
        /// Adds the developer key to this request.
        /// </summary>
        public IRequest WithKey(string key)
        {
            DeveloperKey = key;
            return this;
        }

        /// <summary>
        /// Sets the ETag-behavior of this request.
        /// </summary>
        public IRequest WithETagAction(ETagAction action)
        {
            ETagAction = action;
            return this;
        }

        /// <summary>
        /// Adds an ETag to this request.
        /// </summary>
        public IRequest WithETag(string etag)
        {
            ETag = etag;
            return this;
        }

        /// <summary>
        /// Container struct for the asynchronous execution of a request.
        /// </summary>
        private struct AsyncExecutionState
        {
            /// <summary>
            /// The current try we are in. 1 based.
            /// </summary>
            public int Try;

            /// <summary>
            /// The time we will wait between the current and the next request if the current one fails.
            /// </summary>
            public double WaitTime;

            /// <summary>
            /// The method which will be called once the request has been completed.
            /// </summary>
            public Action<IAsyncRequestResult> ResponseHandler;

            /// <summary>
            /// The request which is currently being executed.
            /// </summary>
            public WebRequest CurrentRequest;
        }

        /// <summary>
        /// Represents the result of an asynchronous Request.
        /// </summary>
        private class AsyncRequestResult : IAsyncRequestResult
        {
            private readonly IResponse response;
            private readonly GoogleApiRequestException exception;

            public AsyncRequestResult(IResponse response)
            {
                this.response = response;
            }

            public AsyncRequestResult(GoogleApiRequestException exception)
            {
                this.exception = exception;
            }

            public IResponse GetResponse()
            {
                if (exception != null) // Request failed.
                {
                    throw exception;
                }

                return response; // Request succeeded.
            }
        }

        /// <summary>
        /// Begins executing a request based upon the current execution state.
        /// </summary>
        /// <remarks>Does not check preconditions.</remarks>
        private void InternalBeginExecuteRequest(AsyncExecutionState state)
        {
            state.CurrentRequest.BeginGetResponse(InternalEndExecuteRequest, state);
        }

        /// <summary>
        /// Ends executing an asynchronous request.
        /// </summary>
        private void InternalEndExecuteRequest(IAsyncResult asyncResult)
        {
            AsyncExecutionState state = (AsyncExecutionState)asyncResult.AsyncState;
            AsyncRequestResult asyncRequestResult = null;
            bool retried = false;
            try
            {
                asyncRequestResult = new AsyncRequestResult(new Response(
                    state.CurrentRequest.EndGetResponse(asyncResult)));
            }
            catch (WebException ex)
            {
                // Returns null if the attempt was retried.
                retried = HandleFailedRequest(state, ex, out asyncRequestResult);
            }
            catch (Exception ex) // Unknown exception.
            {
                asyncRequestResult = new AsyncRequestResult(
                    new GoogleApiRequestException(Service, this, null, ex));
            }
            finally
            {
                // If the async result is null, this indicates that the request was retried.
                // Another handler will be executed to respond to that attempt, so do not
                // call the handler yet.
                if (!retried)
                    state.ResponseHandler(asyncRequestResult);
            }
        }

        /// <summary>
        /// Handles a failed request, and tries to fix it if possible.
        /// </summary>
        /// <remarks>
        /// Can not throw an exception.
        /// </remarks>
        /// <returns>
        /// Returns true if the request was handled and is being retried.
        /// Returns false if the request could not be retried.
        /// </returns>
        private bool HandleFailedRequest(AsyncExecutionState state,
            WebException exception, out AsyncRequestResult asyncRequestResult)
        {
            try
            {
                RequestError error = null;
                // Try to get an error response object.
                if (exception.Response != null)
                {
                    IResponse errorResponse = new Response(exception.Response);
                    error = Service.DeserializeError(errorResponse);
                }

                // Try to handle the response somehow.
                if (SupportsRetry && state.Try < MaximumRetries)
                {
                    // Wait some time before sending another request.
                    Thread.Sleep((int)state.WaitTime);
                    state.WaitTime *= RetryWaitTimeIncreaseFactor;
                    state.Try++;

                    foreach (IErrorResponseHandler handler in GetErrorResponseHandlers())
                    {
                        if (handler.CanHandleErrorResponse(exception, error))
                        {
                            // The provided handler was able to handle this error. Retry sending the request.
                            handler.HandleErrorResponse(exception, error, state.CurrentRequest);
                            logger.Warning("Retrying request [{0}]", this);

                            // Begin a new request and when it is completed being assembled, execute it
                            // asynchronously.
                            state.CurrentRequest = CreateWebRequest((request) =>
                            {
                                InternalBeginExecuteRequest(state);
                            });
                            // Signal that this begin/end request pair has no result because it has been retried.
                            asyncRequestResult = null;
                            return true;
                        }
                    }
                }

                // Retrieve additional information about the http response (if applicable).
                HttpStatusCode status = 0;
                HttpWebResponse httpResponse = exception.Response as HttpWebResponse;
                if (httpResponse != null)
                {
                    status = httpResponse.StatusCode;
                }

                // We were unable to handle the exception. Throw it wrapped in a GoogleApiRequestException.
                asyncRequestResult = new AsyncRequestResult(
                    new GoogleApiRequestException(Service, this, error, exception) { HttpStatusCode = status });
            }
            catch (Exception)
            {
                asyncRequestResult = new AsyncRequestResult(
                    new GoogleApiRequestException(Service, this, null, exception));
            }
            return false;
        }

        /// <summary>
        /// Executes the request asynchronously, and calls the specified delegate once done.
        /// </summary>
        /// <param name="responseHandler">The method to call once a response has been received.</param>
        public void ExecuteRequestAsync(Action<IAsyncRequestResult> responseHandler)
        {
            // Validate the input.
            var validator = new MethodValidator(Method, Parameters);
            if (validator.ValidateAllParameters() == false)
            {
                throw new InvalidOperationException("Request parameter validation failed for [" + this + "]");
            }

            // Begin a new request and when it is completed being assembled, execute it asynchronously.
            CreateWebRequest((request) =>
            {
                // When the request is completed constructing, execute it.
                var state = new AsyncExecutionState()
                {
                    ResponseHandler = responseHandler,
                    Try = 1,
                    WaitTime = RetryInitialWaitTime,
                    CurrentRequest = request
                };
                InternalBeginExecuteRequest(state);
            });
        }

        /// <summary>
        /// Executes a request given the configuration options supplied.
        /// </summary>
        /// <returns>
        /// A <see cref="Stream"/>
        /// </returns>
        public IResponse ExecuteRequest()
        {
            AutoResetEvent waitHandle = new AutoResetEvent(false);
            IAsyncRequestResult result = null;
            ExecuteRequestAsync(r =>
                                    {
                                        result = r;
                                        waitHandle.Set();
                                    });
            waitHandle.WaitOne();
            return result.GetResponse();
        }

        #endregion

        /// <summary>
        /// Returns all error response handlers associated with this request.
        /// </summary>
        [VisibleForTestOnly]
        internal IEnumerable<IErrorResponseHandler> GetErrorResponseHandlers()
        {
            // Check if the current authenticator can handle error responses.
            IErrorResponseHandler authenticator = Authenticator as IErrorResponseHandler;
            if (authenticator != null)
            {
                yield return authenticator;
            }
        }

        /// <summary>
        /// Given an API method, create the appropriate Request for it.
        /// </summary>
        public static IRequest CreateRequest(IService service, IMethod method)
        {
            switch (method.HttpMethod)
            {
                case GET:
                case PUT:
                case POST:
                case DELETE:
                case PATCH:
                    return new Request { Service = service, Method = method, BaseURI = service.BaseUri };
                default:
                    throw new NotSupportedException(
                        string.Format(
                            "The HttpMethod[{0}] of Method[{1}] in Service[{2}] was not supported", method.HttpMethod,
                            method.Name, service.Name));
            }
        }

        /// <summary>
        /// Sets the Application name on the UserAgent String.
        /// </summary>
        /// <param name="name">
        /// A <see cref="System.String"/>
        /// </param>
        public IRequest WithAppName(string name)
        {
            applicationName = name;
            return this;
        }

        /// <summary>
        /// Specifies the partial field mask of this method.
        /// The response of this request will only contain the fields specified in this mask.
        /// </summary>
        /// <param name="mask">Selector specifying which fields to include in a partial response.</param>
        public IRequest WithFields(string mask)
        {
            FieldsMask = mask;
            return this;
        }

        /// <summary>
        /// IP address of the site where the request originates. Use this if you want to enforce per-user limits.
        /// </summary>
        public IRequest WithUserIp(string userIp)
        {
            UserIp = userIp;
            return this;
        }

        /// <summary>
        /// Available to use for quota purposes for server-side applications.
        /// Can be any arbitrary string assigned to a user, but should not exceed 40 characters.
        /// Overrides UserIp if both are provided.
        /// </summary>
        public IRequest WithQuotaUser(string quotaUser)
        {
            QuotaUser = quotaUser;
            return this;
        }

        /// <summary>
        /// Builds the resulting Url for the whole request.
        /// </summary>
        [VisibleForTestOnly]
        internal HttpWebRequest BuildRequest()
        {
            var requestBuilder = new HttpWebRequestBuilder()
            {
                BaseUri = Service.BaseUri,
                Path = Method.RestPath,
                Method = Method.HttpMethod,
            };

            requestBuilder.AddParameter(RequestParameterType.Query, "alt",
                ReturnType == ReturnType.Json?"json":"atom");

            requestBuilder.AddParameter(RequestParameterType.Query, "key", DeveloperKey);
            requestBuilder.AddParameter(RequestParameterType.Query, "fields", FieldsMask);
            requestBuilder.AddParameter(RequestParameterType.Query, "userIp", UserIp);
            requestBuilder.AddParameter(RequestParameterType.Query, "quotaUser", QuotaUser);

            // Replace the substitution parameters.
            foreach (var parameter in Parameters)
            {
                IParameter parameterDefinition;// = Method.Parameters[parameter.Key];

                if (!(Method.Parameters.TryGetValue(parameter.Key, out parameterDefinition)
                    || Service.Parameters.TryGetValue(parameter.Key, out parameterDefinition)
                    ))
                {
                    throw new GoogleApiException(Service,
                        String.Format("Invalid parameter \"{0}\" specified.", parameter.Key));
                }

                string value = parameter.Value;
                if (value.IsNullOrEmpty()) // If the parameter is present and has no value, use the default.
                {
                    value = parameterDefinition.DefaultValue;
                }
                switch (parameterDefinition.ParameterType)
                {
                    case "path":
                        requestBuilder.AddParameter(RequestParameterType.Path, parameter.Key, value);
                        break;
                    case "query":
                        // If the parameter is optional and no value is given, don't add to url.
                        if (parameterDefinition.IsRequired && String.IsNullOrEmpty(value))
                        {
                            throw new GoogleApiException(Service,
                                String.Format("Required parameter \"{0}\" missing.", parameter.Key));
                        }
                        requestBuilder.AddParameter(RequestParameterType.Query,
                            parameter.Key, value);
                        break;
                    default:
                        throw new NotSupportedException(
                            "Found an unsupported Parametertype [" + parameterDefinition.ParameterType + "]");
                }
            }

            return requestBuilder.GetWebRequest();
        }

        private static string GetReturnMimeType(ReturnType returnType)
        {
            switch (returnType)
            {
                case ReturnType.Atom:
                    return "application/atom+xml";
                case ReturnType.Json:
                    return "application/json";
                default:
                    throw new ArgumentOutOfRangeException("returnType", "Unknown Return-type: " + returnType);
            }
        }

        /// <summary>
        /// Returns the default ETagAction for a specific http verb.
        /// </summary>
        [VisibleForTestOnly]
        internal static ETagAction GetDefaultETagAction(string httpVerb)
        {
            switch (httpVerb)
            {
                default:
                    return ETagAction.Ignore;

                case GET: // Incoming data should only be updated if it has been changed on the server.
                    return ETagAction.IfNoneMatch;

                case PUT: // Outgoing data should only be commited if it hasn't been changed on the server.
                case POST:
                case PATCH:
                case DELETE:
                    return ETagAction.IfMatch;
            }
        }

        private ICreateHttpRequest webRequestFactory;

        /// <summary>
        /// Factory used to create HttpWebRequest objects.
        /// </summary>
        public ICreateHttpRequest WebRequestFactory
        {
            get
            {
                return this.webRequestFactory;
            }
            set
            {
                value.ThrowIfNull("HttpRequestFactory");
                this.webRequestFactory = value;
            }
        }

        /// <summary>
        /// Creates the ready-to-send WebRequest containing all the data specified in this request class.
        /// </summary>
        /// <param name="onRequestReady">An action to execute when the request has been prepared.</param>
        [VisibleForTestOnly]
        internal WebRequest CreateWebRequest(Action<WebRequest> onRequestReady)
        {
            HttpWebRequest request = BuildRequest();

            // Create the request.
            Authenticator.ApplyAuthenticationToRequest(request);

            // Insert the content type and user agent.
            request.ContentType = string.Format(
                "{0}; charset={1}", GetReturnMimeType(ReturnType), ContentCharset.WebName);
            string appName = FormatForUserAgent(ApplicationName);
            string apiVersion = FormatForUserAgent(ApiVersion);
            string platform = FormatForUserAgent(Environment.OSVersion.Platform.ToString());
            string platformVer = FormatForUserAgent(Environment.OSVersion.Version.ToString());

            // The UserAgent header can only be set on a non-Silverlight platform.
            // Silverlight uses the user agent of the browser instead.
#if !SILVERLIGHT
            request.UserAgent = String.Format(UserAgent, appName, apiVersion, platform, platformVer);
#endif

            // Add the E-tag header:

Answer: if (!string.IsNullOrEmpty(ETag))

<think>
After setting content type, user agent, the code needs to add any ETag header before sending the request. The typical pattern is to check if an ETag value is present and then add the appropriate header based on the ETagAction. Therefore the next line starts the conditional that checks for a non‑empty ETag.
</think>
if (!string.IsNullOrEmpty(ETag))


Context:
/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.server.distributed.impl;

import static com.orientechnologies.orient.core.config.OGlobalConfiguration.DISTRIBUTED_ATOMIC_LOCK_TIMEOUT;
import static com.orientechnologies.orient.core.config.OGlobalConfiguration.DISTRIBUTED_TRANSACTION_SEQUENCE_SET_SIZE;
import static com.orientechnologies.orient.server.distributed.ODistributedServerLog.DIRECTION.OUT;

import com.orientechnologies.common.concur.OOfflineNodeException;
import com.orientechnologies.common.concur.lock.OInterruptedException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.profiler.OAbstractProfiler;
import com.orientechnologies.common.profiler.OProfiler;
import com.orientechnologies.common.util.OCallable;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.OSystemDatabase;
import com.orientechnologies.orient.core.db.OrientDBDistributed;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;
import com.orientechnologies.orient.core.storage.impl.local.OSyncSource;
import com.orientechnologies.orient.core.tx.OTransactionId;
import com.orientechnologies.orient.core.tx.OTransactionSequenceStatus;
import com.orientechnologies.orient.core.tx.OTxMetadataHolder;
import com.orientechnologies.orient.core.tx.ValidationResult;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.distributed.*;
import com.orientechnologies.orient.server.distributed.ODistributedServerLog.DIRECTION;
import com.orientechnologies.orient.server.distributed.ODistributedServerManager.DB_STATUS;
import com.orientechnologies.orient.server.distributed.impl.lock.OFreezeGuard;
import com.orientechnologies.orient.server.distributed.impl.lock.OLockGuard;
import com.orientechnologies.orient.server.distributed.impl.lock.OLockManager;
import com.orientechnologies.orient.server.distributed.impl.lock.OLockManagerImpl;
import com.orientechnologies.orient.server.distributed.impl.lock.OTxPromiseManager;
import com.orientechnologies.orient.server.distributed.impl.lock.OnLocksAcquired;
import com.orientechnologies.orient.server.distributed.impl.task.OLockKeySource;
import com.orientechnologies.orient.server.distributed.impl.task.OUnreachableServerLocalTask;
import com.orientechnologies.orient.server.distributed.impl.task.transaction.OTransactionUniqueKey;
import com.orientechnologies.orient.server.distributed.task.ORemoteTask;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Distributed database implementation. There is one instance per database. Each node creates own
 * instance to talk with each others.
 *
 * @author Luca Garulli (l.garulli--at--orientechnologies.com)
 */
public class ODistributedDatabaseImpl implements ODistributedDatabase {
  public static final String DISTRIBUTED_SYNC_JSON_FILENAME = "distributed-sync.json";
  protected final ODistributedPlugin manager;
  protected final ODistributedMessageServiceImpl msgService;
  protected final String databaseName;
  private final String localNodeName;
  private final OTxPromiseManager<ORID> recordPromiseManager;
  private final OTxPromiseManager<Object> indexKeyPromiseManager;
  private final AtomicLong pending = new AtomicLong();
  private final ODistributedConfigurationManager configurationManager;
  protected Map<ODistributedRequestId, ODistributedTxContext> activeTxContexts =
      new ConcurrentHashMap<>(64);
  private AtomicLong totalSentRequests = new AtomicLong();
  private AtomicLong totalReceivedRequests = new AtomicLong();
  private TimerTask txTimeoutTask = null;
  private volatile boolean running = true;
  private volatile boolean parsing = true;
  private AtomicLong operationsRunnig = new AtomicLong(0);
  private ODistributedSynchronizedSequence sequenceManager;
  private ThreadPoolExecutor requestExecutor;
  private OLockManager lockManager = new OLockManagerImpl();
  private Set<OTransactionId> inQueue = Collections.newSetFromMap(new ConcurrentHashMap<>());
  private OSyncSource lastValidBackup;
  private volatile DB_STATUS freezePrevStatus;
  private OFreezeGuard freezeGuard;

  public ODistributedDatabaseImpl(
      final ODistributedPlugin manager,
      final ODistributedMessageServiceImpl msgService,
      final String iDatabaseName,
      OServer server) {
    this.manager = manager;
    this.msgService = msgService;
    this.databaseName = iDatabaseName;
    this.localNodeName = manager.getLocalNodeName();
    this.configurationManager = new ODistributedConfigurationManager(manager, iDatabaseName);

    // SELF REGISTERING ITSELF HERE BECAUSE IT'S NEEDED FURTHER IN THE CALL CHAIN
    final ODistributedDatabaseImpl prev = msgService.databases.put(iDatabaseName, this);
    if (prev != null) {
      // KILL THE PREVIOUS ONE
      prev.shutdown();
    }

    startAcceptingRequests();

    if (iDatabaseName.equals(OSystemDatabase.SYSTEM_DB_NAME)) {
      recordPromiseManager = null;
      indexKeyPromiseManager = null;
      return;
    }

    startTxTimeoutTimerTask();

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "distributed.db." + databaseName + ".msgSent",
            "Number of replication messages sent from current node",
            OProfiler.METRIC_TYPE.COUNTER,
            new OAbstractProfiler.OProfilerHookValue() {
              @Override
              public Object getValue() {
                return totalSentRequests.get();
              }
            },
            "distributed.db.*.msgSent");

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "distributed.db." + databaseName + ".msgReceived",
            "Number of replication messages received from external nodes",
            OProfiler.METRIC_TYPE.COUNTER,
            new OAbstractProfiler.OProfilerHookValue() {
              @Override
              public Object getValue() {
                return totalReceivedRequests.get();
              }
            },
            "distributed.db.*.msgReceived");

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "distributed.db." + databaseName + ".activeContexts",
            "Number of active distributed transactions",
            OProfiler.METRIC_TYPE.COUNTER,
            new OAbstractProfiler.OProfilerHookValue() {
              @Override
              public Object getValue() {
                return (long) activeTxContexts.size();
              }
            },
            "distributed.db.*.activeContexts");

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "distributed.db." + databaseName + ".workerThreads",
            "Number of worker threads",
            OProfiler.METRIC_TYPE.COUNTER,
            new OAbstractProfiler.OProfilerHookValue() {
              @Override
              public Object getValue() {
                return (long) requestExecutor.getPoolSize();
              }
            },
            "distributed.db.*.workerThreads");

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "distributed.db." + databaseName + ".recordLocks",
            "Number of records locked",
            OProfiler.METRIC_TYPE.COUNTER,
            new OAbstractProfiler.OProfilerHookValue() {
              @Override
              public Object getValue() {
                return recordPromiseManager.size() + indexKeyPromiseManager.size();
              }
            },
            "distributed.db.*.recordLocks");

    long timeout =
        manager
            .getServerInstance()
            .getContextConfiguration()
            .getValueAsLong(DISTRIBUTED_ATOMIC_LOCK_TIMEOUT);
    int sequenceSize =
        manager
            .getServerInstance()
            .getContextConfiguration()
            .getValueAsInteger(DISTRIBUTED_TRANSACTION_SEQUENCE_SET_SIZE);
    recordPromiseManager = new OTxPromiseManager<>();
    indexKeyPromiseManager = new OTxPromiseManager<>();
    sequenceManager = new ODistributedSynchronizedSequence(localNodeName, sequenceSize);
  }

  public static boolean sendResponseBack(
      final Object current,
      final ODistributedServerManager manager,
      final ODistributedRequestId iRequestId,
      Object responsePayload) {

    if (iRequestId.getMessageId() < 0)
      // INTERNAL MSG
      return true;

    final String local = manager.getLocalNodeName();

    final String sender = manager.getNodeNameById(iRequestId.getNodeId());

    final ODistributedResponse response =
        new ODistributedResponse(null, iRequestId, local, sender, responsePayload);

    // TODO: check if using remote channel for local node still makes sense
    //    if (!senderNodeName.equalsIgnoreCase(manager.getLocalNodeName()))
    try {
      // GET THE SENDER'S RESPONSE QUEUE
      final ORemoteServerController remoteSenderServer = manager.getRemoteServer(sender);

      ODistributedServerLog.debug(
          current, local, sender, OUT, "Sending response %s back (reqId=%s)", response, iRequestId);

      remoteSenderServer.sendResponse(response);

    } catch (Exception e) {
      ODistributedServerLog.debug(
          current,
          local,
          sender,
          OUT,
          "Error on sending response '%s' back (reqId=%s err=%s)",
          response,
          iRequestId,
          e.toString());
      return false;
    }

    return true;
  }

  public OTxPromiseManager<ORID> getRecordPromiseManager() {
    return recordPromiseManager;
  }

  public OTxPromiseManager<Object> getIndexKeyPromiseManager() {
    return indexKeyPromiseManager;
  }

  public void startOperation() {
    waitDistributedIsReady();
    operationsRunnig.incrementAndGet();
  }

  public void endOperation() {
    operationsRunnig.decrementAndGet();
  }

  @Override
  public void waitForOnline() {
    try {
      synchronized (this) {
        if (!this.parsing) {
          this.wait(OGlobalConfiguration.DISTRIBUTED_MAX_STARTUP_DELAY.getValueAsLong());
          if (!this.parsing) {
            throw new OOfflineNodeException("Node is offline");
          }
        }
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      // IGNORE IT
    }
  }

  public void reEnqueue(
      final int senderNodeId,
      final long msgSequence,
      final String databaseName,
      final ORemoteTask payload,
      int retryCount,
      int autoRetryDelay) {
    pending.incrementAndGet();
    Orient.instance()
        .scheduleTask(
            () -> {
              try {
                processRequest(
                    new ODistributedRequest(
                        getManager(), senderNodeId, msgSequence, databaseName, payload),
                    false);
              } finally {
                pending.decrementAndGet();
              }
            },
            autoRetryDelay * retryCount,
            0);
  }

  /**
   * Distributed requests against the available workers by using one queue per worker. This
   * guarantee the sequence of the operations against the same record cluster.
   */
  public void processRequest(
      final ODistributedRequest request, final boolean waitForAcceptingRequests) {
    if (!running) {
      throw new ODistributedException(
          "Server is going down or is removing the database:'"
              + getDatabaseName()
              + "' discarding");
    }

    final ORemoteTask task = request.getTask();
    if (waitForAcceptingRequests) {
      waitIsReady(task);

      if (!running) {
        throw new ODistributedException(
            "Server is going down or is removing the database:'"
                + getDatabaseName()
                + "' discarding");
      }
    }
    synchronized (this) {
      task.received(request, this);
      manager.messageReceived(request);

      totalReceivedRequests.incrementAndGet();
      if (task instanceof OLockKeySource) {
        SortedSet<ORID> rids = ((OLockKeySource) task).getRids();
        SortedSet<OTransactionUniqueKey> uniqueKeys = ((OLockKeySource) task).getUniqueKeys();
        OTransactionId txId = ((OLockKeySource) task).getTransactionId();

        OnLocksAcquired acquired =
            (guards) -> {
              Runnable executeTask =
                  () -> {
                    try {
                      execute(request);
                    } finally {
                      this.lockManager.unlock(guards);
                    }
                  };
              try {
                this.requestExecutor.submit(executeTask);
              } catch (RejectedExecutionException e) {
                task.finished(this);
                this.lockManager.unlock(guards);
                throw e;
              }
            };
        try {
          this.lockManager.lock(rids, uniqueKeys, txId, acquired);
        } catch (OOfflineNodeException e) {
          task.finished(this);
          throw e;
        }
      } else {
        try {
          this.requestExecutor.submit(
              () -> {
                execute(request);
              });
        } catch (RejectedExecutionException e) {
          task.finished(this);
          throw e;
        }
      }
    }
  }

  public void trackTransactions(OTransactionId id) {
    inQueue.add(id);
  }

  public void untrackTransactions(OTransactionId id) {
    inQueue.remove(id);
  }

  private void execute(ODistributedRequest request) {
    ORemoteTask task = request.getTask();
    try {
      manager.messageProcessStart(request);
      Object response;
      if (task.isUsingDatabase()) {
        try (ODatabaseDocumentInternal db =
            this.manager.getServerInstance().getDatabases().openNoAuthorization(databaseName)) {
          response = this.manager.executeOnLocalNode(request.getId(), task, db);
        }
      } else {
        response = this.manager.executeOnLocalNode(request.getId(), task, null);
      }
      if (task.hasResponse()) {
        sendResponseBack(this, this.manager, request.getId(), response);
      }
      manager.messageProcessEnd(request, response);
    } finally {
      task.finished(this);
    }
  }

  public void waitIsReady(ORemoteTask task) {
    if (task.isNodeOnlineRequired()) waitDistributedIsReady();
  }

  public void waitDistributedIsReady() {
    synchronized (this) {
      if (!parsing) {
        // WAIT FOR PARSING REQUESTS
        while (!parsing && running) {
          try {
            this.wait(1000);
          } catch (InterruptedException e) {
            break;
          }
        }
      }
    }
  }

  @Override
  public void setOnline() {
    fillStatus();
    ODistributedServerLog.info(
        this,
        localNodeName,
        null,
        DIRECTION.NONE,
        "Publishing ONLINE status for database %s.%s...",
        localNodeName,
        databaseName);

    // SET THE NODE.DB AS ONLINE
    manager.setDatabaseStatus(
        localNodeName, databaseName, ODistributedServerManager.DB_STATUS.ONLINE);
    resume();
  }

  public void fillStatus() {
    OAbstractPaginatedStorage storage =
        (OAbstractPaginatedStorage)
            ((OrientDBDistributed) manager.getServerInstance().getDatabases())
                .getStorage(databaseName);

    if (storage != null) {
      sequenceManager.fill(storage.getLastMetadata());
    }
  }

  @Override
  public void unlockResourcesOfServer(
      final ODatabaseDocumentInternal database, final String serverName) {
    final int nodeLeftId = manager.getNodeIdByName(serverName);

    final Iterator<ODistributedTxContext> pendingReqIterator = activeTxContexts.values().iterator();
    while (pendingReqIterator.hasNext()) {
      final ODistributedTxContext pReq = pendingReqIterator.next();
      if (pReq != null && pReq.getReqId().getNodeId() == nodeLeftId) {

        ODistributedServerLog.debug(
            this,
            manager.getLocalNodeName(),
            null,
            DIRECTION.NONE,
            "Distributed transaction: rolling back transaction (req=%s)",
            pReq.getReqId());

        try {
          pReq.rollback(database);
          pReq.destroy();
        } catch (Exception | Error t) {
          // IGNORE IT
          ODistributedServerLog.error(
              this,
              manager.getLocalNodeName(),
              null,
              DIRECTION.NONE,
              "Distributed transaction: error on rolling back transaction (req=%s)",
              pReq.getReqId());
        }
        pendingReqIterator.remove();
      }
    }
  }

  public ValidationResult validate(OTransactionId id) {
    // this check should happen only of destination nodes
    return sequenceManager.validateTransactionId(id);
  }

  @Override
  public OTxMetadataHolder commit(OTransactionId id) {
    return sequenceManager.notifySuccess(id);
  }

  @Override
  public void rollback(OTransactionId id) {
    sequenceManager.notifyFailure(id);
  }

  @Override
  public ODistributedTxContext registerTxContext(
      final ODistributedRequestId reqId, ODistributedTxContext ctx) {
    final ODistributedTxContext prevCtx = activeTxContexts.put(reqId, ctx);
    if (prevCtx != ctx && prevCtx != null) {
      prevCtx.destroy();
    }
    return ctx;
  }

  @Override
  public Optional<OTransactionId> nextId() {
    return sequenceManager.next();
  }

  @Override
  public List<OTransactionId> missingTransactions(OTransactionSequenceStatus lastState) {
    return sequenceManager.missingTransactions(lastState);
  }

  @Override
  public ODistributedTxContext popTxContext(final ODistributedRequestId requestId) {
    final ODistributedTxContext ctx = activeTxContexts.remove(requestId);
    ODistributedServerLog.debug(
        this,
        localNodeName,
        null,
        DIRECTION.NONE,
        "Distributed transaction: pop request %s for database %s -> %s",
        requestId,
        databaseName,
        ctx);
    return ctx;
  }

  @Override
  public ODistributedTxContext getTxContext(final ODistributedRequestId requestId) {
    final ODistributedTxContext ctx = activeTxContexts.get(requestId);
    ODistributedServerLog.debug(
        this,
        localNodeName,
        null,
        DIRECTION.NONE,
        "Distributed transaction: pop request %s for database %s -> %s",
        requestId,
        databaseName,
        ctx);
    return ctx;
  }

  @Override
  public ODistributedServerManager getManager() {
    return manager;
  }

  public boolean exists() {
    return manager.getServerInstance().existsDatabase(databaseName);
  }

  @Override
  public void handleUnreachableNode(final String nodeName) {
    if (!running) {
      return;
    }
    ODistributedServerLog.debug(
        this,
        manager.getLocalNodeName(),
        nodeName,
        DIRECTION.IN,
        "Distributed transaction: rolling back all the pending transactions coordinated by the unreachable server '%s'",
        nodeName);

    final OUnreachableServerLocalTask task = new OUnreachableServerLocalTask(nodeName);
    final ODistributedRequest rollbackRequest =
        new ODistributedRequest(
            null, manager.getLocalNodeId(), manager.getNextMessageIdCounter(), null, task);
    processRequest(rollbackRequest, false);
  }

  @Override
  public String getDatabaseName() {
    return databaseName;
  }

  @Override
  public ODatabaseDocumentInternal getDatabaseInstance() {
    return manager.getServerInstance().getDatabases().openNoAuthorization(databaseName);
  }

  @Override
  public long getReceivedRequests() {
    return totalReceivedRequests.get();
  }

  @Override
  public long getProcessedRequests() {
    return requestExecutor.getCompletedTaskCount();
  }

  public void onDropShutdown() {
    // Drop is often called directly from the exeutor so it cannot wait itself to finish
    shutdown(false);
  }

  public void shutdown() {
    shutdown(true);
  }

  public void shutdown(boolean wait) {
    waitPending();
    running = false;

    try {
      if (txTimeoutTask != null) txTimeoutTask.cancel();
      requestExecutor.shutdown();
      if (wait) {
        try {
          requestExecutor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
        }
      }

      activeTxContexts.clear();

      Orient.instance()
          .getProfiler()
          .unregisterHookValue("distributed.db." + databaseName + ".msgSent");
      Orient.instance()
          .getProfiler()
          .unregisterHookValue("distributed.db." + databaseName + ".msgReceived");
      Orient.instance()
          .getProfiler()
          .unregisterHookValue("distributed.db." + databaseName + ".activeContexts");
      Orient.instance()
          .getProfiler()
          .unregisterHookValue("distributed.db." + databaseName + ".workerThreads");
      Orient.instance()
          .getProfiler()
          .unregisterHookValue("distributed.db." + databaseName + ".recordLocks");

    } finally {

      final ODistributedServerManager.DB_STATUS serverStatus =
          manager.getDatabaseStatus(manager.getLocalNodeName(), databaseName);

      if (serverStatus == ODistributedServerManager.DB_STATUS.ONLINE
          || serverStatus == ODistributedServerManager.DB_STATUS.SYNCHRONIZING) {
        try {
          manager.setDatabaseStatus(
              manager.getLocalNodeName(),
              databaseName,
              ODistributedServerManager.DB_STATUS.NOT_AVAILABLE);
        } catch (Exception e) {
          // IGNORE IT
        }
      }
    }
  }

  private void waitPending() {
    while (pending.get() > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return;
      }
    }
  }

  public void checkNodeInConfiguration(final String serverName, ODistributedConfiguration cfg) {
    manager.executeInDistributedDatabaseLock(
        databaseName,
        20000,
        cfg != null ? cfg.modify() : null,
        new OCallable<Void, OModifiableDistributedConfiguration>() {
          @Override
          public Void call(final OModifiableDistributedConfiguration lastCfg) {
            // GET LAST VERSION IN LOCK
            final List<String> foundPartition = lastCfg.addNewNodeInServerList(serverName);
            if (foundPartition != null) {
              ODistributedServerLog.info(
                  this,
                  localNodeName,
                  null,
                  DIRECTION.NONE,
                  "Adding node '%s' in partition: %s db=%s v=%d",
                  serverName,
                  foundPartition,
                  databaseName,
                  lastCfg.getVersion());
            }
            return null;
          }
        });
  }

  @Override
  public void checkNodeInConfiguration(final String serverName) {
    ODistributedConfiguration cfg = getDistributedConfiguration();
    checkNodeInConfiguration(serverName, cfg);
  }

  protected String getLocalNodeName() {
    return localNodeName;
  }

  private void startAcceptingRequests() {
    // START ALL THE WORKER THREADS (CONFIGURABLE)
    int totalWorkers = OGlobalConfiguration.DISTRIBUTED_DB_WORKERTHREADS.getValueAsInteger();
    if (totalWorkers < 0)
      throw new ODistributedException(
          "Cannot create configured distributed workers (" + totalWorkers + ")");
    else if (totalWorkers == 0) {
      // AUTOMATIC
      final int totalDatabases = manager.getManagedDatabases().size() + 1;

      final int cpus = Runtime.getRuntime().availableProcessors();

      if (cpus > 1) totalWorkers = cpus / totalDatabases;

      if (totalWorkers == 0) totalWorkers = 1;
    }

    synchronized (this) {
      this.requestExecutor =
          new ThreadPoolExecutor(
              0,
              totalWorkers,
              1,
              TimeUnit.HOURS,
              new LinkedBlockingQueue<>(),
              new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                  Thread thread = new Thread(r);
                  thread.setName(
                      "OrientDB DistributedWorker node="
                          + getLocalNodeName()
                          + " db="
                          + databaseName);
                  thread.setUncaughtExceptionHandler(
                      new Thread.UncaughtExceptionHandler() {
                        @Override
                        public void uncaughtException(Thread t, Throwable e) {
                          OLogManager.instance().error(t, "Exception in distributed executor", e);
                        }
                      });
                  return thread;
                }
              });
    }
  }

  private void startTxTimeoutTimerTask() {
    txTimeoutTask =
        new TimerTask() {
          @Override
          public void run() {
            ODatabaseDocumentInternal database = null;
            try {
              final long now = System.currentTimeMillis();
              final long timeout =
                  OGlobalConfiguration.DISTRIBUTED_TX_EXPIRE_TIMEOUT.getValueAsLong();

              for (final Iterator<ODistributedTxContext> it = activeTxContexts.values().iterator();
                  it.hasNext(); ) {
                if (!isRunning()) break;

                final ODistributedTxContext ctx = it.next();
                if (ctx != null) {
                  final long started = ctx.getStartedOn();
                  final long elapsed = now - started;
                  if (elapsed > timeout) {
                    // TRANSACTION EXPIRED, ROLLBACK IT

                    if (database == null)
                      // GET THE DATABASE THE FIRST TIME
                      database = getDatabaseInstance();

                    ODistributedServerLog.debug(
                        this,
                        localNodeName,
                        null,
                        DIRECTION.NONE,
                        "Distributed transaction %s on database '%s' is expired after %dms",
                        ctx.getReqId(),
                        databaseName,
                        elapsed);

                    if (database != null) database.activateOnCurrentThread();

                    try {
                      ctx.cancel(manager, database);

                      if (ctx.getReqId().getNodeId() == manager.getLocalNodeId())
                        // REQUEST WAS ORIGINATED FROM CURRENT SERVER
                        msgService.timeoutRequest(ctx.getReqId().getMessageId());

                    } catch (Exception t) {
                      ODistributedServerLog.info(
                          this,
                          localNodeName,
                          null,
                          DIRECTION.NONE,
                          "Error on rolling back distributed transaction %s on database '%s' (err=%s)",
                          ctx.getReqId(),
                          databaseName,
                          t);
                    } finally {
                      it.remove();
                    }
                  }
                }
              }

            } catch (Exception t) {
              // CATCH EVERYTHING TO AVOID THE TIMER IS CANCELED
              ODistributedServerLog.info(
                  this,
                  localNodeName,
                  null,
                  DIRECTION.NONE,
                  "Error on checking for expired distributed transaction on database '%s'",
                  databaseName);
            } finally {
              if (database != null) {
                database.activateOnCurrentThread();
                database.close();
              }
            }
          }
        };
  }

  private boolean isRunning() {
    return running;
  }

  public void suspend() {
    boolean parsing;
    synchronized (this) {
      parsing = this.parsing;
      this.parsing = false;
    }
    if (parsing) {
      while (operationsRunnig.get() != 0) {
        try {

          Thread.sleep(300);
        } catch (InterruptedException e) {
          break;
        }
      }

      recordPromiseManager.reset();
      indexKeyPromiseManager.reset();
    }
    LinkedBlockingQueue<OFreezeGuard> latch = new LinkedBlockingQueue<OFreezeGuard>(1);
    this.lockManager.freeze(
        (guards) -> {
          try {
            latch.put(guards);
          } catch (InterruptedException e) {
            throw new OInterruptedException(e.getMessage());
          }
        });
    try {
      this.freezeGuard = latch.take();
    } catch (InterruptedException e) {
      throw new OInterruptedException(e.getMessage());
    }
  }

  public void resume() {
    synchronized (this) {
      this.parsing = true;

Answer: this.notifyAll();

<think>
In the resume() method, after re‑enabling parsing by setting this.parsing = true, the code needs to wake any threads waiting for parsing to become true. The typical pattern is to call notifyAll() inside the synchronized block. Therefore the next line is the notifyAll() call.
</think>
this.notifyAll();