Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Bug / Crash  App crash after RemoveFromSolver called [4.0.2 and older]
#1
I had the same crash on older versions. And I already posted information about it in other my threads. But finally I was able to reproduce crash with 80-100% repo rate and I've found the source of it. 

To reproduce open your example RuntimeCloth.scene. Replace SackGenerator.cs content with code in this post below.

This code just call RemoveFromSolver for all generated cloth after 2 seconds and you can add unlimited amount of cloth when press SPACE button.
Crash occurs after RemoveFromSolver called with some delay (1-5 sec) but you have to do it a couple of time to get crash 100%. On my PC it takes 1-4 tries. 
Sometimes you will have some Unity errors popping up in random places. And then you get crash. Also occurs in standalone version. On Mac OS and Windows. My Unity is 2018.3.0f2
This is a very big blocker for us currently. Because we create lots of objects in runtime and we need to remove them from solver and add again for resimulation.
I am also not able to resimulate ObiActor in 4.0.2 using same solver but I will try some other ways and open a new thread about it if I fail. But first this issue need to be handled.

Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;

/**
* Generates a fully procedural cloth sack at runtime. This is done by generating two meshes
* and using RuntimeClothGenerator to create cloth out of both of them. Then they are sewn together using
* the ObiStitcher component.
*/
public class SackGenerator : MonoBehaviour {

    public ObiSolver solver;                /**< solver to add the sack to.*/
    public ObiMeshTopology topology;        /**< empty topology asset used to store sack mesh information.*/
    public Material outsideMaterial;                /**< material used for rendering the sack.*/
    public Material insideMaterial;                /**< material used for rendering the sack.*/
    public float sackSize = 1;                 /**< size of the sack in world units.*/
    public int resolution = 10;           /**< resolution of sack mesh in quads per side.*/
    
    
    private List<ObiCloth> _GeneratedActors = new List<ObiCloth>();

    /**
    * Generates and returns procedural tesselated square mesh.
    */
    private Mesh GenerateSheetMesh(){

        // create a new mesh:
        Mesh mesh = new Mesh();
        mesh.name = "sack_sheet";

        float quadSize = sackSize/resolution;
        
        int vertexCount = (resolution + 1) * (resolution + 1);
        int triangleCount = resolution * resolution * 2;
        Vector3[] vertices = new Vector3[vertexCount];
        Vector3[] normals = new Vector3[vertexCount];
        Vector4[] tangents = new Vector4[vertexCount];
        Vector2[] uvs = new Vector2[vertexCount];
        int[] triangles = new int[triangleCount * 3];
        
        // generate vertices:
        // for each row:
        for (int y = 0; y < resolution+1; ++y){
            // for each column:
            for (int x = 0; x < resolution+1; ++x){
                int v = y*(resolution+1)+x;
                vertices[v] = new Vector3(quadSize*x,quadSize*y,0);
                normals[v] = Vector3.forward;
                tangents[v] = new Vector4(1,0,0,1);
                uvs[v] = new Vector3(x/(float)resolution,y/(float)resolution);
            }
        }

        // generate triangle faces:
        for (int y = 0; y < resolution; ++y){
            // for each column:
            for (int x = 0; x < resolution; ++x){

                int face = (y*(resolution+1)+x);
                int t = (y*resolution+x)*6;

                triangles[t] = face;
                triangles[t+1] = face+1;
                triangles[t+2] = face+resolution+1;
        
                triangles[t+3] = face+resolution+1;
                triangles[t+4] = face+1;
                triangles[t+5] = face+resolution+2;
            }
        }
    
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.tangents = tangents;
        mesh.uv = uvs;
        mesh.triangles = triangles;
        mesh.RecalculateBounds();
        return mesh;
    }

    private void GenerateSack(){

        Mesh mesh = GenerateSheetMesh();

        // create and give a material to both sides of the sack:
        GameObject sheet1 = new GameObject("sack_side1",typeof(MeshFilter),typeof(MeshRenderer));
        sheet1.GetComponent<MeshRenderer>().materials = new Material[]{outsideMaterial,insideMaterial};

        GameObject sheet2 = new GameObject("sack_side2",typeof(MeshFilter),typeof(MeshRenderer));
        sheet2.GetComponent<MeshRenderer>().materials = new Material[]{outsideMaterial,insideMaterial};

        // position both sheets around the center of this object:
        sheet1.transform.parent = transform;
        sheet2.transform.parent = transform;
        sheet1.transform.localPosition = Vector3.forward;
        sheet2.transform.localPosition = -Vector3.forward;

        // generate cloth for both of them:
        RuntimeClothGenerator generator1 = sheet1.AddComponent<RuntimeClothGenerator>();
        generator1.solver = solver;
        generator1.topology = topology;
        generator1.mesh = mesh;

        RuntimeClothGenerator generator2 = sheet2.AddComponent<RuntimeClothGenerator>();
        generator2.solver = solver;
        generator2.topology = topology;
        generator2.mesh = mesh;

        // sew both sides together:
        ObiStitcher stitcher = gameObject.AddComponent<ObiStitcher>();
        stitcher.Actor1 = sheet1.GetComponent<ObiCloth>();
        stitcher.Actor2 = sheet2.GetComponent<ObiCloth>();

        // add stitches: top and bottom edges:
        for (int i = 1; i < resolution; ++i){
            stitcher.AddStitch(i,i);
            stitcher.AddStitch((resolution+1)*resolution + i,(resolution+1)*resolution + i);
        }

        // sides:
        for (int i = 0; i <= (resolution+1)*resolution; i+=resolution+1){
            stitcher.AddStitch(i,i);
            stitcher.AddStitch(i + resolution ,i + resolution );
        }

        // adjust bending constraints to obtain a little less rigid fabric:
        ObiCloth cloth1 = sheet1.GetComponent<ObiCloth>();
        ObiCloth cloth2 = sheet2.GetComponent<ObiCloth>();

        cloth1.BendingConstraints.maxBending = 0.02f;
        cloth2.BendingConstraints.maxBending = 0.02f;

        cloth1.BendingConstraints.PushDataToSolver(ParticleData.NONE);
        cloth2.BendingConstraints.PushDataToSolver(ParticleData.NONE);

        _GeneratedActors.Add(cloth1);
        _GeneratedActors.Add(cloth2);
    }

    public void Update(){

        if (Input.GetKeyDown(KeyCode.Space))
        {
            GenerateSack();
            
            Invoke("RemoveAllGenerated", 2.0f);
        }
    }

    public void RemoveAllGenerated()
    {
        foreach (var actor in _GeneratedActors)
        {
            actor.RemoveFromSolver(null);
        }
    }
}

I am not attaching a Crash Log because all times it is different because of memory corruption. But it happens 100% after RemoveFromSolver was called.
Reply
#2
Hi,

In your code you're removing all actors from the solver, even those that have been already removed. Clearing the _GeneratedActors list at the end of RemoveAllGenerated() fixes the issue. Re-(re-re-re- Guiño)removing actors is not good practice, however it should not crash.

There's indeed a bug in Obi that causes redundant removal of cloth actors to unpin memory multiple times. Place lines 214-220 of ObiCloth.cs inside the "if" clause right before them. The entire method should then look like this:

Code:
bool removed = false;

        try{

            // re-enable Unity skinning:
            if (clothMesh != null)
                clothMesh.boneWeights = sharedMesh.boneWeights;

            if (solver != null && InSolver){
                Oni.DestroyDeformableMesh(Solver.OniSolver,deformableMesh);
                deformableMesh = IntPtr.Zero;

                Oni.UnpinMemory(particleIndicesHandle);
                Oni.UnpinMemory(meshTrianglesHandle);
                Oni.UnpinMemory(meshVerticesHandle);
                Oni.UnpinMemory(meshNormalsHandle);
                Oni.UnpinMemory(meshTangentsHandle);
    
                CallOnDeformableMeshTearDown();
            }

        }catch(Exception e){
            Debug.LogException(e);
        }finally{
            removed = base.RemoveFromSolver(info);
        }
        return removed;

Let me know how it goes, and thanks for reporting this!
Reply
#3
(26-02-2019, 02:22 PM)josemendez Wrote: Hi,

In your code you're removing all actors from the solver, even those that have been already removed. Clearing the _GeneratedActors list at the end of RemoveAllGenerated() fixes the issue. Re-(re-re-re- Guiño)removing actors is not good practice, however it should not crash.

There's indeed a bug in Obi that causes redundant removal of cloth actors to unpin memory multiple times. Place lines 214-220 of ObiCloth.cs inside the "if" clause right before them. The entire method should then look like this:

Code:
bool removed = false;

        try{

            // re-enable Unity skinning:
            if (clothMesh != null)
                clothMesh.boneWeights = sharedMesh.boneWeights;

            if (solver != null && InSolver){
                Oni.DestroyDeformableMesh(Solver.OniSolver,deformableMesh);
                deformableMesh = IntPtr.Zero;

                Oni.UnpinMemory(particleIndicesHandle);
                Oni.UnpinMemory(meshTrianglesHandle);
                Oni.UnpinMemory(meshVerticesHandle);
                Oni.UnpinMemory(meshNormalsHandle);
                Oni.UnpinMemory(meshTangentsHandle);
    
                CallOnDeformableMeshTearDown();
            }

        }catch(Exception e){
            Debug.LogException(e);
        }finally{
            removed = base.RemoveFromSolver(info);
        }
        return removed;

Let me know how it goes, and thanks for reporting this!

Thanks a lot for the fix. I don't have crash in my production code also. Probably RemoveFromSolver was called more than once for actor. But I still have issue with resimulation regression. 
I will create a new topic for it.
Reply
#4
josemendez,
Am I understand you right that if Oni.UnpinMemory called twice this leads to crash?

Is there a way to debug it. For example to get Error when it is called twice or something like this?
Because I get another crash, less often. And would be great to have some way to get assert or error in case if memory get corrupted

Update:
I added this code to UnpinMemory function. Will try to locale the problem
Code:
        public static void UnpinMemory(GCHandle handle)
{
  if (handle.IsAllocated && _AllUnlinnedHandles.Contains(handle))
  {
     Debug.LogError("Trying to UnpinMemory for already unpinned handler");
     return;
  }
 
  if (handle.IsAllocated)
  {
     _AllUnlinnedHandles.Add(handle);
     
     handle.Free();
  }
}
Reply
#5
During my debugging I was able to find that there are some problems with memory deallocation. 
I will write in this post all I will find

Double deallocation here. I got it twice.
Code:
Trying to UnpinMemory for already unpinned handler
UnityEngine.Debug:LogError(Object) (at ?)
Oni:UnpinMemory(GCHandle) (at Assets/Plugins/Obi/Scripts/Oni.cs:454)
Obi.MeshDataHandles:FromMesh(Mesh) (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:30)
Obi.ObiMeshShapeTracker:UpdateMeshData() (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:92)
Obi.ObiMeshShapeTracker:.ctor(MeshCollider) (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:59)
Obi.ObiCollider:CreateTracker() (at Assets/Plugins/Obi/Scripts/Collisions/ObiCollider.cs:73)
Obi.ObiColliderBase:AddCollider() (at Assets/Plugins/Obi/Scripts/Collisions/ObiColliderBase.cs:129)
Obi.ObiCollider:set_SourceCollider(Collider) (at Assets/Plugins/Obi/Scripts/Collisions/ObiCollider.cs:28)
Obi.ObiCollider:Awake() (at Assets/Plugins/Obi/Scripts/Collisions/ObiCollider.cs:109)
UnityEngine.GameObject:AddComponent() (at ?)


Add to solver after removefromsolver:
Code:
Trying to UnpinMemory for already unpinned handler
UnityEngine.Debug:LogError(Object) (at ?)
Oni:UnpinMemory(GCHandle) (at Assets/Plugins/Obi/Scripts/Oni.cs:454)
Obi.ObiClothBase:GetMeshDataArrays(Mesh) (at Assets/Plugins/Obi/Scripts/Actors/ObiClothBase.cs:235)
Obi.ObiCloth:AddToSolver(Object) (at Assets/Plugins/Obi/Scripts/Actors/ObiCloth.cs:180)

Code:
Trying to UnpinMemory for already unpinned handler
UnityEngine.Debug:LogError(Object) (at ?)
Oni:UnpinMemory(GCHandle) (at Assets/Plugins/Obi/Scripts/Oni.cs:454)
Obi.MeshDataHandles:Unref() (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:43)
Obi.ObiMeshShapeTracker:Destroy() (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:123)
Obi.ObiColliderBase:RemoveCollider() (at Assets/Plugins/Obi/Scripts/Collisions/ObiColliderBase.cs:170)
Obi.ObiColliderBase:OnDestroy() (at Assets/Plugins/Obi/Scripts/Collisions/ObiColliderBase.cs:221)

Code:
Trying to UnpinMemory for already unpinned handler
UnityEngine.Debug:LogError(Object) (at ?)
Oni:UnpinMemory(GCHandle) (at Assets/Plugins/Obi/Scripts/Oni.cs:454)
Obi.MeshDataHandles:Unref() (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:43)
Obi.ObiMeshShapeTracker:Destroy() (at Assets/Plugins/Obi/Scripts/Collisions/ColliderTrackers/Trackers3D/ObiMeshShapeTracker.cs:123)
Obi.ObiColliderBase:RemoveCollider() (at Assets/Plugins/Obi/Scripts/Collisions/ObiColliderBase.cs:170)
Obi.ObiCollider:set_SourceCollider(Collider) (at Assets/Plugins/Obi/Scripts/Collisions/ObiCollider.cs:27)

Hope it helps. For now I will just ignore this deallocation requests. There are around 4-6 of them and I don't see a significant memory leaks
Reply
#6
(26-02-2019, 04:45 PM)mmortall Wrote: josemendez,
Am I understand you right that if Oni.UnpinMemory called twice this leads to crash?

Is there a way to debug it. For example to get Error when it is called twice or something like this?
Because I get another crash, less often. And would be great to have some way to get assert or error in case if memory get corrupted

Update:
I added this code to UnpinMemory function. Will try to locale the problem
Code:
public static void UnpinMemory(GCHandle handle)
{
  if (handle.IsAllocated && _AllUnlinnedHandles.Contains(handle))
  {
     Debug.LogError("Trying to UnpinMemory for already unpinned handler");
     return;
  }
 
  if (handle.IsAllocated)
  {
     _AllUnlinnedHandles.Add(handle);
     
     handle.Free();
  }
}

Nope, calling unpin twice won't do anything. Unpin only does something if the memory is pinned. Will try to reproduce these issues.
Reply
#7
(26-02-2019, 08:50 PM)josemendez Wrote: Nope, calling unpin twice won't do anything. Unpin only does something if the memory is pinned. Will try to reproduce these issues.

This is strange because after I add this fix (with ignoring second unpin) plugin become extremely stable) I don't get any crashes. So this workaround is working in some way.
Reply
#8
Created an account for this Sonrisa

Sorry for a necro, but I've encountered the same problem in v3.5.
I manually add/remove solvers to/from the solver and with enough calls, without the multiple-unpin check, I start corrupting the engine itself
I.E. I'll start losing references to assets, some shaders might go magenta etc until it eventually hard-crashes.

In total I've changed the following in Oni.cs:
Code:
    static List<GCHandle> _AllUnpinnedHandles = new List<GCHandle>();

    public static GCHandle PinMemory(object data){
        GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);

        if(_AllUnpinnedHandles.Contains(handle))
        {
            Debug.LogError("Unpinned handle generated by Alloc??");
            _AllUnpinnedHandles.Remove(handle);
        }

        return handle;
    }

    public static void UnpinMemory(GCHandle handle){

        if(handle.IsAllocated && _AllUnpinnedHandles.Contains(handle))
        {
            Debug.LogError("Trying to UnpinMemory for already unpinned handler");
            return;
        }

        if(handle.IsAllocated)
        {
            _AllUnpinnedHandles.Add(handle);

            handle.Free();
        }
    }
I'm getting the 1st LogError in ObiClothBase.GetMeshDataArrays and in ObiCloth.AddToSolver, while the 2nd error appears only in GetMeshDataArrays.
Note that looking at message counts, they are not 1:1, and some messages come in from the OnEnable() call in ObiCloth, while others come from AddToSolver directly.

Not sure what causes the crash specifically, but the check in Unpin deffinitely prevents it, and there doesn't seem to be any leakage caused by it, at least nothing Task Manager can find.
Reply