/// <summary>
        /// Template method for common code needed in UpdateServerInfo, UpdatePlayers and UpdateRules
        /// </summary>
        private bool ExecuteUpdate(UpdateRequest request, ServerRow row, Server server, Func <Action <int>, bool> updater)
        {
            if (request.IsCancelled)
            {
                return(false);
            }

            try
            {
                row.Status = "updating " + row.Retries;
                request.SetDataModified();
                bool ok = updater(retry =>
                {
                    if (request.IsCancelled)
                    {
                        throw new OperationCanceledException();
                    }
                    if (row.Retries == 0)
                    {
                        Interlocked.Increment(ref request.TasksWithRetries);
                    }
                    row.Status = "updating " + (++row.Retries + 1);
                    request.SetDataModified();
                });
                if (ok)
                {
                    return(true);
                }
                Interlocked.Increment(ref request.TasksWithTimeout);
            }
            catch
            {
            }
            return(false);
        }
        // shared update code

        #region UpdateServerAndDetails()
        private void UpdateServerAndDetails(UpdateRequest request, ServerRow row, bool fireRefreshSingleServerComplete)
        {
            string status = "";

            try
            {
                if (request.IsCancelled)
                {
                    return;
                }

                using (Server server = ServerQuery.GetServerInstance(EngineType.Source, row.EndPoint, false, request.Timeout, request.Timeout))
                {
                    row.Retries = 0;
                    server.SendFirstPacketTwice = this.sendFirstUdpPacketTwice;
                    server.Retries = 3;
                    status         = "timeout";
                    if (this.UpdateServerInfo(request, row, server))
                    {
                        row.GameExtension = gameExtensions.Get((Game)(row.ServerInfo.Extra?.GameId ?? row.ServerInfo.Id));
                        this.UpdatePlayers(request, row, server);
                        this.UpdateRules(request, row, server);
                        status = "ok";
                    }
                    row.RequestTimestamp = request.Timestamp;
                }

                if (request.IsCancelled) // status might have changed
                {
                    return;
                }

                if (row.Retries > 0)
                {
                    status += " (" + row.Retries + ")";
                }
            }
            catch
            {
                // this happens when you hibernate windows and the program resumes before the network connection has be reestablished
                status = "network error";
            }
            finally
            {
                row.Status = status;
                row.Update();
                request.SetDataModified();

                if (fireRefreshSingleServerComplete && !request.IsCancelled)
                {
                    this.RefreshSingleServerComplete?.Invoke(this, new ServerEventArgs(row));
                }

                if (!request.PendingTasks.IsSet)
                {
                    request.PendingTasks.Signal();
                }
            }
        }
        private void OnMasterServerReceive(UpdateRequest request, ReadOnlyCollection <Tuple <IPEndPoint, ServerInfo> > endPoints, Exception error, bool isPartialResult)
        {
            if (request.IsCancelled)
            {
                return;
            }

            string statusText;
            string errorMsg = error?.Message ?? (endPoints == null ? "Timeout" : null);

            if (errorMsg != null)
            {
                statusText = "Error requesting server list from master server: " + errorMsg;
                if (request.receivedServerCount > 0)
                {
                    this.AllServersReceived(request);
                }
            }
            else
            {
                statusText = $"Updating status of {endPoints.Count} servers...";
                foreach (var ep in endPoints)
                {
                    if (request.IsCancelled)
                    {
                        return;
                    }

                    if (++request.receivedServerCount > request.MaxResults)
                    {
                        statusText = $"Server list limited to {request.MaxResults} entries. Updating status...";
                        break;
                    }
                    else if (request.GameExtension.AcceptGameServer(ep.Item1))
                    {
                        request.Servers.Add(new ServerRow(ep.Item1, request.GameExtension, ep.Item2));
                    }
                }

                if (!isPartialResult)
                {
                    this.AllServersReceived(request);
                }
            }

            request.SetDataModified();
            this.UpdateStatus?.Invoke(this, new TextEventArgs(statusText));
        }
        private void UpdateServerAndDetails(UpdateRequest request, ServerRow row, bool fireRefreshSingleServerComplete)
        {
            string status = "";
              try
              {
            if (request.IsCancelled)
              return;

            using (Server server = ServerQuery.GetServerInstance(EngineType.Source, row.EndPoint, false, request.Timeout, request.Timeout))
            {
              row.Retries = 0;
              server.SendFirstPacketTwice = this.sendFirstUdpPacketTwice;
              server.Retries = 3;
              status = "timeout";
              var oldPing = row.ServerInfo?.Ping ?? 0;
              if (this.UpdateServerInfo(request, row, server))
              {
            if (request.KeepPreviousPing && row.ServerInfo != null)
              row.ServerInfo.Ping = oldPing; // keep old ping so that the row isn't immediately resorted and moved
            this.UpdatePlayers(request, row, server);
            this.UpdateRules(request, row, server);
            status = "ok";
              }
              row.RequestTimestamp = request.Timestamp;
            }

            if (request.IsCancelled) // status might have changed
              return;

            if (row.Retries > 0)
              status += " (" + row.Retries + ")";
              }
              catch
              {
            // this happens when you hibernate windows and the program resumes before the network connection has be reestablished
            status = "network error";
              }
              finally
              {
            row.Status = status;
            row.Update();
            request.SetDataModified();

            if (fireRefreshSingleServerComplete)
              this.RefreshSingleServerComplete?.Invoke(this, new ServerEventArgs(row));

            if (!request.PendingTasks.IsSet)
              request.PendingTasks.Signal();
              }
        }
        private void OnMasterServerReceive(UpdateRequest request, ReadOnlyCollection<IPEndPoint> endPoints)
        {
            if (request.IsCancelled)
            return;

              string statusText;
              if (endPoints == null)
              {
            statusText = $"Master server request timed out after returning {request.receivedServerCount} servers (clients are limited to receive 30 packets per minute)";
            if (request.receivedServerCount > 0)
              this.AllServersReceived(request);
              }
              else
              {
            statusText = $"Requesting batch {++request.BatchNumber} of server list...";
            foreach (var ep in endPoints)
            {
              if (request.IsCancelled)
            return;
              if (ep.Address.Equals(IPAddress.Any))
              {
            statusText = $"Master server returned {request.Servers.Count} servers";
            this.AllServersReceived(request);
              }
              else if (++request.receivedServerCount >= request.MaxResults)
              {
            statusText = $"Server list limited to {request.MaxResults} entries";
            this.AllServersReceived(request);
            break;
              }
              else if (request.GameExtension.AcceptGameServer(ep))
            request.Servers.Add(new ServerRow(ep, request.GameExtension));
            }
              }

              request.SetDataModified();
              this.UpdateStatus?.Invoke(this, new TextEventArgs(statusText));
        }
        /// <summary>
        /// Template method for common code needed in UpdateServerInfo, UpdatePlayers and UpdateRules
        /// </summary>
        private bool ExecuteUpdate(UpdateRequest request, ServerRow row, Server server, Func<Action<int>, bool> updater)
        {
            if (request.IsCancelled)
            return false;

              try
              {
            row.Status = "updating " + row.Retries;
            request.SetDataModified();
            bool ok = updater(retry =>
            {
              if (request.IsCancelled)
            throw new OperationCanceledException();
              if (row.Retries == 0)
            Interlocked.Increment(ref request.TasksWithRetries);
              row.Status = "updating " + (++row.Retries + 1);
              request.SetDataModified();
            });
            if (ok)
              return true;
            Interlocked.Increment(ref request.TasksWithTimeout);
              }
              catch
              {
              }
              return false;
        }