mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-28 23:00:07 +01:00
f914f03c21
- Fixed the way we calculate PercentComplete for ParallelAction: use the sum of actions in actionsByConnection and actionsWithNoConnection instead of subActions.Count, to avoid waiting for the completion of actions that we never start, because we exclude actions with disconnected connections from actionsByConnections
217 lines
9.4 KiB
C#
217 lines
9.4 KiB
C#
/* Copyright (c) Citrix Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using XenAdmin.Network;
|
|
using XenAPI;
|
|
|
|
|
|
namespace XenAdmin.Actions
|
|
{
|
|
/// <summary>
|
|
/// ParallelAction takes a list of any number of actions and runs a certain number of them simultaneously.
|
|
/// Once one simultaneous action is finished the next one in the queue is started until all are complete
|
|
/// </summary>
|
|
public class ParallelAction : MultipleAction
|
|
{
|
|
//Change parameter to increase the number of concurrent actions running
|
|
private const int DEFAULT_MAX_NUMBER_OF_PARALLEL_ACTIONS = 25;
|
|
|
|
private Dictionary<IXenConnection, List<AsyncAction>> actionsByConnection = new Dictionary<IXenConnection, List<AsyncAction>>();
|
|
private Dictionary<IXenConnection, ProduceConsumerQueue> queuesByConnection = new Dictionary<IXenConnection, ProduceConsumerQueue>();
|
|
|
|
private List<AsyncAction> actionsWithNoConnection = new List<AsyncAction>();
|
|
private ProduceConsumerQueue queueWithNoConnection;
|
|
|
|
private readonly int maxNumberOfParallelActions;
|
|
private int actionsCount;
|
|
|
|
public ParallelAction(IXenConnection connection, string title, string startDescription, string endDescription, List<AsyncAction> subActions, bool suppressHistory, bool showSubActionsDetails, int maxNumberOfParallelActions = DEFAULT_MAX_NUMBER_OF_PARALLEL_ACTIONS)
|
|
: base(connection, title, startDescription, endDescription, subActions, suppressHistory, showSubActionsDetails)
|
|
{
|
|
if (Connection != null)
|
|
{
|
|
actionsByConnection.Add(Connection, subActions);
|
|
actionsCount = subActions.Count;
|
|
}
|
|
else
|
|
GroupActionsByConnection();
|
|
this.maxNumberOfParallelActions = maxNumberOfParallelActions;
|
|
}
|
|
|
|
public ParallelAction(IXenConnection connection, string title, string startDescription, string endDescription, List<AsyncAction> subActions, int maxNumberOfParallelActions = DEFAULT_MAX_NUMBER_OF_PARALLEL_ACTIONS)
|
|
: this(connection, title, startDescription, endDescription, subActions, false, false, maxNumberOfParallelActions)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Use this constructor to create a cross connection ParallelAction.
|
|
/// It takes a list of any number of actions, separates them by connections
|
|
/// and runs a certain number of them simultaneously on each connection, all connections in parallel.
|
|
/// Once one simultaneous action is finished the next one in the queue is started until all are complete.
|
|
/// </summary>
|
|
public ParallelAction(string title, string startDescription, string endDescription, List<AsyncAction> subActions, bool suppressHistory, bool showSubActionsDetails, int maxNumberOfParallelActions = DEFAULT_MAX_NUMBER_OF_PARALLEL_ACTIONS)
|
|
: base(null, title, startDescription, endDescription, subActions, suppressHistory, showSubActionsDetails)
|
|
{
|
|
GroupActionsByConnection();
|
|
this.maxNumberOfParallelActions = maxNumberOfParallelActions;
|
|
}
|
|
|
|
public ParallelAction(string title, string startDescription, string endDescription, List<AsyncAction> subActions, int maxNumberOfParallelActions = DEFAULT_MAX_NUMBER_OF_PARALLEL_ACTIONS)
|
|
: this(title, startDescription, endDescription, subActions, false, false, maxNumberOfParallelActions)
|
|
{ }
|
|
|
|
private void GroupActionsByConnection()
|
|
{
|
|
actionsCount = 0;
|
|
foreach (AsyncAction action in subActions)
|
|
{
|
|
if (action.Connection != null)
|
|
{
|
|
if (action.Connection.IsConnected)
|
|
{
|
|
if (!actionsByConnection.ContainsKey(action.Connection))
|
|
{
|
|
actionsByConnection.Add(action.Connection, new List<AsyncAction>());
|
|
}
|
|
|
|
actionsByConnection[action.Connection].Add(action);
|
|
actionsCount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actionsWithNoConnection.Add(action);
|
|
actionsCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
protected override void RunSubActions(List<Exception> exceptions)
|
|
{
|
|
foreach (IXenConnection connection in actionsByConnection.Keys)
|
|
{
|
|
queuesByConnection[connection] = new ProduceConsumerQueue(Math.Min(maxNumberOfParallelActions, actionsByConnection[connection].Count));
|
|
foreach (AsyncAction subAction in actionsByConnection[connection])
|
|
{
|
|
EnqueueAction(subAction, queuesByConnection[connection], exceptions);
|
|
}
|
|
}
|
|
|
|
if (actionsWithNoConnection.Count > 0)
|
|
queueWithNoConnection = new ProduceConsumerQueue(Math.Min(maxNumberOfParallelActions, actionsWithNoConnection.Count));
|
|
|
|
foreach (AsyncAction subAction in actionsWithNoConnection)
|
|
{
|
|
EnqueueAction(subAction, queueWithNoConnection, exceptions);
|
|
}
|
|
|
|
lock (_lock)
|
|
{
|
|
Monitor.Wait(_lock);
|
|
}
|
|
}
|
|
|
|
void EnqueueAction(AsyncAction action, ProduceConsumerQueue queue, List<Exception> exceptions)
|
|
{
|
|
action.Completed += action_Completed;
|
|
queue.EnqueueItem(
|
|
() =>
|
|
{
|
|
if (Cancelling) // don't start any more actions
|
|
return;
|
|
try
|
|
{
|
|
action.RunExternal(action.Session);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Failure f = e as Failure;
|
|
if (f != null && Connection != null &&
|
|
f.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
|
|
{
|
|
Failure.ParseRBACFailure(f, action.Connection, action.Session ?? action.Connection.Session);
|
|
}
|
|
exceptions.Add(e);
|
|
// Record the first exception we come to. Though later if there are more than one we will replace this with non specific one.
|
|
if (Exception == null)
|
|
Exception = e;
|
|
}
|
|
});
|
|
}
|
|
|
|
protected override void RecalculatePercentComplete()
|
|
{
|
|
int total = 0;
|
|
foreach (IXenConnection connection in actionsByConnection.Keys)
|
|
{
|
|
foreach (var action in actionsByConnection[connection])
|
|
total += action.PercentComplete;
|
|
}
|
|
foreach (var action in actionsWithNoConnection)
|
|
total += action.PercentComplete;
|
|
PercentComplete = (int)(total / actionsCount);
|
|
}
|
|
|
|
private readonly object _lock = new object();
|
|
private volatile int i = 0;
|
|
|
|
void action_Completed(ActionBase sender)
|
|
{
|
|
sender.Completed -= action_Completed;
|
|
lock (_lock)
|
|
{
|
|
i++;
|
|
if (i == actionsCount)
|
|
{
|
|
Monitor.Pulse(_lock);
|
|
PercentComplete = 100;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void MultipleAction_Completed(ActionBase sender)
|
|
{
|
|
base.MultipleAction_Completed(sender);
|
|
|
|
foreach (IXenConnection connection in queuesByConnection.Keys)
|
|
{
|
|
queuesByConnection[connection].StopWorkers(false);
|
|
}
|
|
|
|
if (queueWithNoConnection != null)
|
|
queueWithNoConnection.StopWorkers(false);
|
|
}
|
|
}
|
|
}
|