Fix avatar parameter updating for viewer 3 and maybe 2.

When a slider parameter is changed, the viewer uploads a new shape (or other asset) and the item is updated to point to it.
Viewer 1 uploaded the data in the initial request itself, so the asset references was almost always correctly updated.
However, viewer 3/2 always uploads data in a subsequent xfer, which exposed a race condition where the viewer would make the item update before the asset had uploaded.
This commit shuffles the order of operations to avoid this race, the item is updated with the new asset id instead of the old one while the upload was still taking place.
A second race had to be fixed where avatar appearance would also be updated with the old asset id rather than the new one.
This was fixed by updating the avatar appearance ids when the appearance was actually saved, rather than when the wearables update was made.
remove-scene-viewer
Justin Clark-Casey (justincc) 2011-09-23 02:59:33 +01:00
parent 41616cad09
commit c8304b7f84
8 changed files with 271 additions and 94 deletions

View File

@ -167,6 +167,7 @@ namespace OpenSim.Framework
get { return m_metadata.FullID; }
set { m_metadata.FullID = value; }
}
/// <summary>
/// Asset MetaData ID (transferring from UUID to string ID)
/// </summary>

View File

@ -225,6 +225,8 @@ namespace OpenSim.Framework
/// </summary>
public virtual void ResetAppearance()
{
// m_log.WarnFormat("[AVATAR APPEARANCE]: Reset appearance");
m_serial = 0;
SetDefaultTexture();

View File

@ -41,14 +41,13 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
/// </summary>
public class AgentAssetTransactions
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// Fields
private bool m_dumpAssetsToFile;
private Scene m_Scene;
public UUID UserID;
public Dictionary<UUID, AssetXferUploader> XferUploaders =
new Dictionary<UUID, AssetXferUploader>();
private UUID UserID;
private Dictionary<UUID, AssetXferUploader> XferUploaders = new Dictionary<UUID, AssetXferUploader>();
// Methods
public AgentAssetTransactions(UUID agentID, Scene scene,
@ -59,36 +58,94 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
m_dumpAssetsToFile = dumpAssetsToFile;
}
public AssetXferUploader RequestXferUploader(UUID transactionID)
/// <summary>
/// Return a xfer uploader if one does not already exist.
/// </summary>
/// <param name="transactionID"></param>
/// <param name="assetID">
/// We must transfer the new asset ID into the uploader on creation, otherwise
/// we can see race conditions with other threads which can retrieve an item before it is updated with the new
/// asset id.
/// </param>
/// <returns>
/// The xfer uploader requested. Null if one is already in existence.
/// FIXME: This is a bizarre thing to do, and is probably meant to signal an error condition if multiple
/// transfers are made. Needs to be corrected.
/// </returns>
public AssetXferUploader RequestXferUploader(UUID transactionID, UUID assetID)
{
if (!XferUploaders.ContainsKey(transactionID))
lock (XferUploaders)
{
AssetXferUploader uploader = new AssetXferUploader(m_Scene,
m_dumpAssetsToFile);
lock (XferUploaders)
if (!XferUploaders.ContainsKey(transactionID))
{
XferUploaders.Add(transactionID, uploader);
}
AssetXferUploader uploader = new AssetXferUploader(this, m_Scene, assetID, m_dumpAssetsToFile);
return uploader;
// m_log.DebugFormat(
// "[AGENT ASSETS TRANSACTIONS]: Adding asset xfer uploader {0} since it didn't previously exist", transactionID);
XferUploaders.Add(transactionID, uploader);
return uploader;
}
}
m_log.WarnFormat("[AGENT ASSETS TRANSACTIONS]: Ignoring request for asset xfer uploader {0} since it already exists", transactionID);
return null;
}
public void HandleXfer(ulong xferID, uint packetID, byte[] data)
{
AssetXferUploader foundUploader = null;
lock (XferUploaders)
{
foreach (AssetXferUploader uploader in XferUploaders.Values)
{
// m_log.DebugFormat(
// "[AGENT ASSETS TRANSACTIONS]: In HandleXfer, inspect xfer upload with xfer id {0}",
// uploader.XferID);
if (uploader.XferID == xferID)
{
uploader.HandleXferPacket(xferID, packetID, data);
foundUploader = uploader;
break;
}
}
}
if (foundUploader != null)
{
// m_log.DebugFormat(
// "[AGENT ASSETS TRANSACTIONS]: Found xfer uploader for xfer id {0}, packet id {1}, data length {2}",
// xferID, packetID, data.Length);
foundUploader.HandleXferPacket(xferID, packetID, data);
}
else
{
m_log.ErrorFormat(
"[AGENT ASSET TRANSACTIONS]: Could not find uploader for xfer id {0}, packet id {1}, data length {2}",
xferID, packetID, data.Length);
}
}
public bool RemoveXferUploader(UUID transactionID)
{
lock (XferUploaders)
{
bool removed = XferUploaders.Remove(transactionID);
if (!removed)
m_log.WarnFormat(
"[AGENT ASSET TRANSACTIONS]: Received request to remove xfer uploader with transaction ID {0} but none found",
transactionID);
// else
// m_log.DebugFormat(
// "[AGENT ASSET TRANSACTIONS]: Removed xfer uploader with transaction ID {0}", transactionID);
return removed;
}
}
public void RequestCreateInventoryItem(IClientAPI remoteClient,
@ -96,36 +153,43 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
string description, string name, sbyte invType,
sbyte type, byte wearableType, uint nextOwnerMask)
{
if (XferUploaders.ContainsKey(transactionID))
AssetXferUploader uploader = null;
lock (XferUploaders)
{
XferUploaders[transactionID].RequestCreateInventoryItem(
remoteClient, transactionID, folderID,
callbackID, description, name, invType, type,
wearableType, nextOwnerMask);
if (XferUploaders.ContainsKey(transactionID))
uploader = XferUploaders[transactionID];
}
if (uploader != null)
uploader.RequestCreateInventoryItem(
remoteClient, transactionID, folderID,
callbackID, description, name, invType, type,
wearableType, nextOwnerMask);
else
m_log.ErrorFormat(
"[AGENT ASSET TRANSACTIONS]: Could not find uploader with transaction ID {0} when handling request to create inventory item {1} from {2}",
transactionID, name, remoteClient.Name);
}
/// <summary>
/// Get an uploaded asset. If the data is successfully retrieved,
/// the transaction will be removed.
/// </summary>
/// <param name="transactionID"></param>
/// <returns>The asset if the upload has completed, null if it has not.</returns>
public AssetBase GetTransactionAsset(UUID transactionID)
private AssetBase GetTransactionAsset(UUID transactionID)
{
if (XferUploaders.ContainsKey(transactionID))
lock (XferUploaders)
{
AssetXferUploader uploader = XferUploaders[transactionID];
AssetBase asset = uploader.GetAssetData();
lock (XferUploaders)
if (XferUploaders.ContainsKey(transactionID))
{
XferUploaders.Remove(transactionID);
}
AssetXferUploader uploader = XferUploaders[transactionID];
AssetBase asset = uploader.GetAssetData();
RemoveXferUploader(transactionID);
return asset;
return asset;
}
}
return null;
@ -135,7 +199,15 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
SceneObjectPart part, UUID transactionID,
TaskInventoryItem item)
{
if (XferUploaders.ContainsKey(transactionID))
AssetXferUploader uploader = null;
lock (XferUploaders)
{
if (XferUploaders.ContainsKey(transactionID))
uploader = XferUploaders[transactionID];
}
if (uploader != null)
{
AssetBase asset = GetTransactionAsset(transactionID);
@ -161,28 +233,34 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
m_Scene.AssetService.Store(asset);
}
}
else
{
m_log.ErrorFormat(
"[AGENT ASSET TRANSACTIONS]: Could not find uploader with transaction ID {0} when handling request to update task inventory item {1} in {2}",
transactionID, item.Name, part.Name);
}
}
public void RequestUpdateInventoryItem(IClientAPI remoteClient,
UUID transactionID, InventoryItemBase item)
{
if (XferUploaders.ContainsKey(transactionID))
AssetXferUploader uploader = null;
lock (XferUploaders)
{
AssetBase asset = GetTransactionAsset(transactionID);
if (XferUploaders.ContainsKey(transactionID))
uploader = XferUploaders[transactionID];
}
if (asset != null)
{
asset.FullID = UUID.Random();
asset.Name = item.Name;
asset.Description = item.Description;
asset.Type = (sbyte)item.AssetType;
item.AssetID = asset.FullID;
m_Scene.AssetService.Store(asset);
IInventoryService invService = m_Scene.InventoryService;
invService.UpdateItem(item);
}
if (uploader != null)
{
uploader.RequestUpdateInventoryItem(remoteClient, transactionID, item);
}
else
{
m_log.ErrorFormat(
"[AGENT ASSET TRANSACTIONS]: Could not find uploader with transaction ID {0} when handling request to update inventory item {1} for {2}",
transactionID, item.Name, remoteClient.Name);
}
}
}

View File

@ -172,11 +172,12 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
/// <summary>
/// Update an inventory item with data that has been received through a
/// transaction.
///
/// </summary>
/// <remarks>
/// This is called when clothing or body parts are updated (for
/// instance, with new textures or colours). It may also be called in
/// other situations.
/// </summary>
/// </remarks>
/// <param name="remoteClient"></param>
/// <param name="transactionID"></param>
/// <param name="item"></param>
@ -184,14 +185,12 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
UUID transactionID, InventoryItemBase item)
{
// m_log.DebugFormat(
// "[TRANSACTIONS MANAGER] Called HandleItemUpdateFromTransaction with item {0}",
// "[ASSET TRANSACTION MODULE]: Called HandleItemUpdateFromTransaction with item {0}",
// item.Name);
AgentAssetTransactions transactions =
GetUserTransactions(remoteClient.AgentId);
AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId);
transactions.RequestUpdateInventoryItem(remoteClient,
transactionID, item);
transactions.RequestUpdateInventoryItem(remoteClient, transactionID, item);
}
/// <summary>
@ -255,11 +254,8 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
}
}
AgentAssetTransactions transactions =
GetUserTransactions(remoteClient.AgentId);
AssetXferUploader uploader =
transactions.RequestXferUploader(transaction);
AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId);
AssetXferUploader uploader = transactions.RequestXferUploader(transaction, assetID);
if (uploader != null)
{
@ -279,9 +275,8 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
public void HandleXfer(IClientAPI remoteClient, ulong xferID,
uint packetID, byte[] data)
{
//m_log.Debug("xferID: " + xferID + " packetID: " + packetID + " data!");
AgentAssetTransactions transactions =
GetUserTransactions(remoteClient.AgentId);
// m_log.Debug("xferID: " + xferID + " packetID: " + packetID + " data length " + data.Length);
AgentAssetTransactions transactions = GetUserTransactions(remoteClient.AgentId);
transactions.HandleXfer(xferID, packetID, data);
}

View File

@ -40,11 +40,21 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Reference to the object that holds this uploader. Used to remove ourselves from it's list if we
/// are performing a delayed update.
/// </summary>
AgentAssetTransactions m_transactions;
private AssetBase m_asset;
private UUID InventFolder = UUID.Zero;
private sbyte invType = 0;
private bool m_createItem = false;
private uint m_createItemCallback = 0;
private bool m_updateItem = false;
private InventoryItemBase m_updateItemData;
private string m_description = String.Empty;
private bool m_dumpAssetToFile;
private bool m_finished = false;
@ -58,9 +68,11 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
public ulong XferID;
private Scene m_Scene;
public AssetXferUploader(Scene scene, bool dumpAssetToFile)
public AssetXferUploader(AgentAssetTransactions transactions, Scene scene, UUID assetID, bool dumpAssetToFile)
{
m_transactions = transactions;
m_Scene = scene;
m_asset = new AssetBase() { FullID = assetID };
m_dumpAssetToFile = dumpAssetToFile;
}
@ -73,6 +85,10 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
/// <returns>True if the transfer is complete, false otherwise or if the xferID was not valid</returns>
public bool HandleXferPacket(ulong xferID, uint packetID, byte[] data)
{
// m_log.DebugFormat(
// "[ASSET XFER UPLOADER]: Received packet {0} for xfer {1} (data length {2})",
// packetID, xferID, data.Length);
if (XferID == xferID)
{
if (m_asset.Data.Length > 1)
@ -107,16 +123,20 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
/// <param name="xferID"></param>
/// <param name="packetID"></param>
/// <param name="data"></param>
/// <returns>True if the transfer is complete, false otherwise</returns>
public bool Initialise(IClientAPI remoteClient, UUID assetID,
public void Initialise(IClientAPI remoteClient, UUID assetID,
UUID transaction, sbyte type, byte[] data, bool storeLocal,
bool tempFile)
{
// m_log.DebugFormat(
// "[ASSET XFER UPLOADER]: Initialised xfer from {0}, asset {1}, transaction {2}, type {3}, storeLocal {4}, tempFile {5}, already received data length {6}",
// remoteClient.Name, assetID, transaction, type, storeLocal, tempFile, data.Length);
ourClient = remoteClient;
m_asset = new AssetBase(assetID, "blank", type,
remoteClient.AgentId.ToString());
m_asset.Data = data;
m_asset.Name = "blank";
m_asset.Description = "empty";
m_asset.Type = type;
m_asset.CreatorID = remoteClient.AgentId.ToString();
m_asset.Data = data;
m_asset.Local = storeLocal;
m_asset.Temporary = tempFile;
@ -126,21 +146,22 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
if (m_asset.Data.Length > 2)
{
SendCompleteMessage();
return true;
}
else
{
RequestStartXfer();
}
return false;
}
protected void RequestStartXfer()
{
XferID = Util.GetNextXferID();
ourClient.SendXferRequest(XferID, m_asset.Type, m_asset.FullID,
0, new byte[0]);
// m_log.DebugFormat(
// "[ASSET XFER UPLOADER]: Requesting Xfer of asset {0}, type {1}, transfer id {2} from {3}",
// m_asset.FullID, m_asset.Type, XferID, ourClient.Name);
ourClient.SendXferRequest(XferID, m_asset.Type, m_asset.FullID, 0, new byte[0]);
}
protected void SendCompleteMessage()
@ -148,18 +169,32 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
ourClient.SendAssetUploadCompleteMessage(m_asset.Type, true,
m_asset.FullID);
m_finished = true;
if (m_createItem)
// We must lock in order to avoid a race with a separate thread dealing with an inventory item or create
// message from other client UDP.
lock (this)
{
DoCreateItem(m_createItemCallback);
}
else if (m_storeLocal)
{
m_Scene.AssetService.Store(m_asset);
m_finished = true;
if (m_createItem)
{
DoCreateItem(m_createItemCallback);
}
else if (m_updateItem)
{
StoreAssetForItemUpdate(m_updateItemData);
// Remove ourselves from the list of transactions if completion was delayed until the transaction
// was complete.
// TODO: Should probably do the same for create item.
m_transactions.RemoveXferUploader(TransactionID);
}
else if (m_storeLocal)
{
m_Scene.AssetService.Store(m_asset);
}
}
m_log.DebugFormat(
"[ASSET TRANSACTIONS]: Uploaded asset {0} for transaction {1}",
"[ASSET XFER UPLOADER]: Uploaded asset {0} for transaction {1}",
m_asset.FullID, TransactionID);
if (m_dumpAssetToFile)
@ -205,18 +240,66 @@ namespace OpenSim.Region.CoreModules.Agent.AssetTransaction
m_asset.Description = description;
m_asset.Type = type;
// We must lock to avoid a race with a separate thread uploading the asset.
lock (this)
{
if (m_finished)
{
DoCreateItem(callbackID);
}
else
{
m_createItem = true; //set flag so the inventory item is created when upload is complete
m_createItemCallback = callbackID;
}
}
}
}
public void RequestUpdateInventoryItem(IClientAPI remoteClient, UUID transactionID, InventoryItemBase item)
{
// We must lock to avoid a race with a separate thread uploading the asset.
lock (this)
{
m_asset.Name = item.Name;
m_asset.Description = item.Description;
m_asset.Type = (sbyte)item.AssetType;
// We must always store the item at this point even if the asset hasn't finished uploading, in order
// to avoid a race condition when the appearance module retrieves the item to set the asset id in
// the AvatarAppearance structure.
item.AssetID = m_asset.FullID;
m_Scene.InventoryService.UpdateItem(item);
if (m_finished)
{
DoCreateItem(callbackID);
StoreAssetForItemUpdate(item);
}
else
{
m_createItem = true; //set flag so the inventory item is created when upload is complete
m_createItemCallback = callbackID;
// m_log.DebugFormat(
// "[ASSET XFER UPLOADER]: Holding update inventory item request {0} for {1} pending completion of asset xfer for transaction {2}",
// item.Name, remoteClient.Name, transactionID);
m_updateItem = true;
m_updateItemData = item;
}
}
}
/// <summary>
/// Store the asset for the given item.
/// </summary>
/// <param name="item"></param>
private void StoreAssetForItemUpdate(InventoryItemBase item)
{
// m_log.DebugFormat(
// "[ASSET XFER UPLOADER]: Storing asset {0} for earlier item update for {1} for {2}",
// m_asset.FullID, item.Name, ourClient.Name);
m_Scene.AssetService.Store(m_asset);
}
private void DoCreateItem(uint callbackID)
{
m_Scene.AssetService.Store(m_asset);

View File

@ -211,6 +211,13 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
// Process the visual params, this may change height as well
if (visualParams != null)
{
// string[] visualParamsStrings = new string[visualParams.Length];
// for (int i = 0; i < visualParams.Length; i++)
// visualParamsStrings[i] = visualParams[i].ToString();
// m_log.DebugFormat(
// "[AVFACTORY]: Setting visual params for {0} to {1}",
// client.Name, string.Join(", ", visualParamsStrings));
float oldHeight = sp.Appearance.AvatarHeight;
changed = sp.Appearance.SetVisualParams(visualParams);
@ -418,6 +425,13 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
// m_log.WarnFormat("[AVFACTORY] avatar {0} save appearance",agentid);
// This could take awhile since it needs to pull inventory
// We need to do it at the point of save so that there is a sufficient delay for any upload of new body part/shape
// assets and item asset id changes to complete.
// I don't think we need to worry about doing this within m_setAppearanceLock since the queueing avoids
// multiple save requests.
SetAppearanceAssets(sp.UUID, sp.Appearance);
m_scene.AvatarService.SetAppearance(agentid, sp.Appearance);
}
@ -504,9 +518,6 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
avatAppearance.GetAssetsFrom(sp.Appearance);
// This could take awhile since it needs to pull inventory
SetAppearanceAssets(sp.UUID, ref avatAppearance);
lock (m_setAppearanceLock)
{
// Update only those fields that we have changed. This is important because the viewer
@ -540,7 +551,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
return true;
}
private void SetAppearanceAssets(UUID userID, ref AvatarAppearance appearance)
private void SetAppearanceAssets(UUID userID, AvatarAppearance appearance)
{
IInventoryService invService = m_scene.InventoryService;

View File

@ -334,6 +334,10 @@ namespace OpenSim.Region.Framework.Scenes
public void UpdateInventoryItemAsset(IClientAPI remoteClient, UUID transactionID,
UUID itemID, InventoryItemBase itemUpd)
{
// m_log.DebugFormat(
// "[USER INVENTORY]: Updating asset for item {0} {1}, transaction ID {2} for {3}",
// itemID, itemUpd.Name, transactionID, remoteClient.Name);
// This one will let people set next perms on items in agent
// inventory. Rut-Roh. Whatever. Make this secure. Yeah.
//
@ -385,8 +389,7 @@ namespace OpenSim.Region.Framework.Scenes
IAgentAssetTransactions agentTransactions = this.RequestModuleInterface<IAgentAssetTransactions>();
if (agentTransactions != null)
{
agentTransactions.HandleItemUpdateFromTransaction(
remoteClient, transactionID, item);
agentTransactions.HandleItemUpdateFromTransaction(remoteClient, transactionID, item);
}
}
}

View File

@ -40,9 +40,9 @@ namespace OpenSim.Services.InventoryService
{
public class XInventoryService : ServiceBase, IInventoryService
{
//private static readonly ILog m_log =
// LogManager.GetLogger(
// MethodBase.GetCurrentMethod().DeclaringType);
// private static readonly ILog m_log =
// LogManager.GetLogger(
// MethodBase.GetCurrentMethod().DeclaringType);
protected IXInventoryData m_Database;
protected bool m_AllowDelete = true;
@ -385,18 +385,22 @@ namespace OpenSim.Services.InventoryService
public virtual bool AddItem(InventoryItemBase item)
{
//m_log.DebugFormat(
// "[XINVENTORY SERVICE]: Adding item {0} to folder {1} for {2}", item.ID, item.Folder, item.Owner);
// m_log.DebugFormat(
// "[XINVENTORY SERVICE]: Adding item {0} to folder {1} for {2}", item.ID, item.Folder, item.Owner);
return m_Database.StoreItem(ConvertFromOpenSim(item));
}
public virtual bool UpdateItem(InventoryItemBase item)
{
// throw new Exception("urrgh");
if (!m_AllowDelete)
if (item.AssetType == (sbyte)AssetType.Link || item.AssetType == (sbyte)AssetType.LinkFolder)
return false;
// m_log.InfoFormat(
// "[XINVENTORY SERVICE]: Updating item {0} {1} in folder {2}", item.Name, item.ID, item.Folder);
return m_Database.StoreItem(ConvertFromOpenSim(item));
}