Do zombies exist ... in .NET?
Asked Answered
S

7

411

I was having a discussion with a teammate about locking in .NET. He's a really bright guy with an extensive background in both lower-level and higher-level programming, but his experience with lower level programming far exceeds mine. Anyway, He argued that .NET locking should be avoided on critical systems expected to be under heavy-load if at all possible in order to avoid the admittedly small possibility of a "zombie thread" crashing a system. I routinely use locking and I didn't know what a "zombie thread" was, so I asked. The impression I got from his explanation is that a zombie thread is a thread that has terminated but somehow still holds onto some resources. An example he gave of how a zombie thread could break a system was a thread begins some procedure after locking on some object, and then is at some point terminated before the lock can be released. This situation has the potential to crash the system, because eventually, attempts to execute that method will result in the threads all waiting for access to an object that will never be returned, because the thread that is using the locked object is dead.

I think I got the gist of this, but if I'm off base, please let me know. The concept made sense to me. I wasn't completely convinced that this was a real scenario that could happen in .NET. I've never previously heard of "zombies", but I do recognize that programmers who have worked in depth at lower levels tend to have a deeper understanding of computing fundamentals (like threading). I definitely do see the value in locking, however, and I have seen many world class programmers leverage locking. I also have limited ability to evaluate this for myself because I know that the lock(obj) statement is really just syntactic sugar for:

bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }

and because Monitor.Enter and Monitor.Exit are marked extern. It seems conceivable that .NET does some kind of processing that protects threads from exposure to system components that could have this kind of impact, but that is purely speculative and probably just based on the fact that I've never heard of "zombie threads" before. So, I'm hoping I can get some feedback on this here:

  1. Is there a clearer definition of a "zombie thread" than what I've explained here?
  2. Can zombie threads occur on .NET? (Why/Why not?)
  3. If applicable, How could I force the creation of a zombie thread in .NET?
  4. If applicable, How can I leverage locking without risking a zombie thread scenario in .NET?

Update

I asked this question a little over two years ago. Today this happened:

Object is in a zombie state.

Stonewort answered 19/11, 2013 at 7:38 Comment(23)
are you sure your co-mate does not talk about deadlocking??Fisch
@AndreasNiedermair - I know what deadlocking is and it was clearly not a matter of misuse of that terminology. Deadlocking was mentioned in the conversation and was clearly distinct from a "zombie thread". To me, the main distinction is that a dead lock has a two-way un-resolvable dependency whereas zombie thread is one-way, and requires a terminated process. If you disagree and think there's a better way to look at these things, please explainStonewort
not necessarily ... you can also leave a lock unreleased ... which is basically the same "circular" behaviour ... but yes, you are true ... it's more a kind of starvation (en.wikipedia.org/wiki/Resource_starvation) :)Fisch
@AndreasNiedermair - I definitely recognize the similarity, but I would be hesitant to call them basically the same. Would you say that the definition of deadlocking and the four necessary conditions at en.wikipedia.org/wiki/Deadlock is inaccurate?Stonewort
@AndreasNiedermair - it definitely does seem closer to that. The example he gave me involved a locked attempt to establish a Socket connection failing - the concept itself isn't 100% clear to me (hence the question)Stonewort
I think it's no relationship on "Lock" or not. any threads that crashed without resource released(or released in an Abnormal way) should be called “zombie thread“. So in my option, abnormal way to release threads is the cause.Snuffbox
@Snuffbox - examples?Stonewort
I think the term "zombie" actually comes from a UNIX backgound, as in "zombie process", right??? There is a clear definition of a "zombie process" in UNIX: it describes a child process that has terminated but where the parent of the child process still needs to "release" the child process (and it's resources) by calling wait or waitpid. The child process is then called a "zombie process". See also howtogeek.com/119815Fealty
@Fealty - What is the correspondence between a UNIX process and a .NET process?Stonewort
My first thought was that it looked like a deadlock indeed, but I can see some differences by reading the answers :)Derinna
This is abuse of the term zombie. In UNIX there is such a thing as a process that is in state Z. It still occupies an entry in the process table and if you have too many of them, the system will no longer be able to create more processes. In this situation with .NET, there is a thread which exits and leaves behind orphaned resources. The thread is not in state Z, it is simply finished and gone. (The thread will stay around in the kernel until after all handles to it are closed, in this case you may describe it as a "zombie" thread.)Frisch
Never seen one. Of all the problems that can arise in multithreaded apps, this 'zombie' thing is low down on the list. It would help a lot with such concerns if devs would stop continually creating, terminating and destroying threads. Just stop doing that, everyone, please, I beg of you!Permissible
If part of your program crashes, leaving the program in an undefined state, then of course that can cause problems with the rest of your program. The same thing can happen if you improperly handle exceptions in a single-threaded program. The problem isn't with threads, the problem is that you have global mutable state and that you're not properly handling unexpected thread termination. Your "really bright" coworker is totally off base on this one.Corrincorrina
"Ever since the first computers, there have always been ghosts in the machine. Random segments of code that have grouped together to form unexpected protocols..."Yonatan
I think there have been enough back-and-forth edits on the title. I'm not fond of the OPs title here, but I think it best to just leave it be, please. Locking for just an hour to keep things stable for now.Blitzkrieg
I agree this is abuse of the term "zombie"; it makes it look like Unix Z processes, but it's misleading (it is clearly a different problem). Moreover, on one point he is definitely wrong: you cannot crash the system. Your process? Maybe. The system? No. If a thread terminates in a non-clean way resources are not released. But if a process terminates non-cleanly, resources DO get freed for you by the OSFrere
The title is fine; "in .NET" is a clear disambiguation that the OP is not talking about unix processes.Justifier
We're gonna need another question "how to crash a system with a zombie thread", of course there are ways, you just gotta learn DirectX(pun intended)Scoggins
Why is this specific to .NET? One can think of having the same issue in other languages...Followthrough
@Followthrough : (1) .NET isn't a language. (2) it was specific to .NET, because the app I'm working on is .NET and my reason for asking was practical, not theoretical. This general issue is certainly applicable to some other runtime environments, but it's definitely not a universal concern for any language. There are languages where the execution environment is at such a level of abstraction that development can even be agnostic to threading altogether. As I'm learning, .NET is works at a high enough level that safety from some threading concerns (e.g. zombie threads) are greatly simplified.Stonewort
Answer to 4: you only need to catch ZombieThreadException to handle this problems, simple. :) Since the zombie apocalypse has not arrived yet :pLoincloth
There are already zombies in .net source code see referencesource.microsoft.com/#q=zombieRoadability
Your update is a native COM object that got into a zombie state, not a .NET object.Thorvaldsen
G
251
  • Is there a clearer definition of a "zombie thread" than what I've explained here?

Seems like a pretty good explanation to me - a thread that has terminated (and can therefore no longer release any resources), but whose resources (e.g. handles) are still around and (potentially) causing problems.

  • Can zombie threads occur on .NET? (Why/Why not?)
  • If applicable, How could I force the creation of a zombie thread in .NET?

They sure do, look, I made one!

[DllImport("kernel32.dll")]
private static extern void ExitThread(uint dwExitCode);

static void Main(string[] args)
{
    new Thread(Target).Start();
    Console.ReadLine();
}

private static void Target()
{
    using (var file = File.Open("test.txt", FileMode.OpenOrCreate))
    {
        ExitThread(0);
    }
}

This program starts a thread Target which opens a file and then immediately kills itself using ExitThread. The resulting zombie thread will never release the handle to the "test.txt" file and so the file will remain open until the program terminates (you can check with process explorer or similar). The handle to "test.txt" won't be released until GC.Collect is called - it turns out it is even more difficult than I thought to create a zombie thread that leaks handles)

  • If applicable, How can I leverage locking without risking a zombie thread scenario in .NET?

Don't do what I just did!

As long as your code cleans up after itself correctly (use Safe Handles or equivalent classes if working with unmanaged resources), and as long as you don't go out of your way to kill threads in weird and wonderful ways (safest way is just to never kill threads - let them terminate themselves normally, or through exceptions if necessary), the only way that you are going to have something resembling a zombie thread is if something has gone very wrong (e.g. something goes wrong in the CLR).

In fact its actually surprisingly difficult to create a zombie thread (I had to P/Invoke into a function that esentially tells you in the documentation not to call it outside of C). For example the following (awful) code actually doesn't create a zombie thread.

static void Main(string[] args)
{
    var thread = new Thread(Target);
    thread.Start();
    // Ugh, never call Abort...
    thread.Abort();
    Console.ReadLine();
}

private static void Target()
{
    // Ouch, open file which isn't closed...
    var file = File.Open("test.txt", FileMode.OpenOrCreate);
    while (true)
    {
        Thread.Sleep(1);
    }
    GC.KeepAlive(file);
}

Despite making some pretty awful mistakes, the handle to "test.txt" is still closed as soon as Abort is called (as part of the finalizer for file which under the covers uses SafeFileHandle to wrap its file handle)

The locking example in C.Evenhuis answer is probably the easiest way to fail to release a resource (a lock in this case) when a thread is terminated in a non-weird way, but thats easily fixed by either using a lock statement instead, or putting the release in a finally block.

See also

Gambier answered 19/11, 2013 at 10:30 Comment(22)
i remember when i played with saving stuff in excel using a backgroundworker i didnt released all the resources all the time (because i just skipped debugging etc). in the taskmanager i saw afterwards about 50 excel processes. did i create zombieexcelprocesses?Rosales
@Gambier - +1 - Great answer. I'm a little skeptical of your ExitThread call though. Obviously, it works, but it feels more like a clever trick than a realistic scenario. One of my goals is to learn what not to do so that I don't accidentally create zombie threads with .NET code. I probably could have figured out that calling C++ code that's known to cause that issue from .NET code would produce the desired effect. You obviously know a lot about this stuff. Do you know of any other cases (possibly weird, but not weird enough to never happen unintentionally) with the same result?Stonewort
@Gambier - the Eric Lippert article makes sense, and I have obviously read it before (there's an excerpt from the second article in my question), but I'm not sure I appreciated it as much before, so I'm going to reread it a couple times now. Can you think of any other edge cases for this that could be stumbled upon accidentallyStonewort
@Stonewort You are 100% correct - ExitThread is definitely not a realistic scenario :) I'm not aware of any way of leaking a handle from .Net code that isn't calling P/Invoke in some way (e.g. indirectly via a 3rd party class), or a bug in the .Net framework.Gambier
@Gambier - Just to clarify that I understood you correctly, would you agree that the addition of the C# lock keyword to any block of code can be guaranteed not to be responsible for creating a zombie thread? (And am I correct in my understanding of Lippert that the only such case for previous C# versions would result from a concurrent thread throwing a ThreadAbortException at exactly the right moment)?Stonewort
@Stonewort Yeah I think you've understood me correctly - as long as you use the lock keyword instead of explicitly using Monitor.Enter and Monitor.Exit then you can be guaranteed that lock will be released when the thread terminates (unless silly P/Invokes are happening), and yes the only such case for previous C# versions is a concurrent thread calling Thread.Abort (as I understand it)Gambier
So the answer is 'not in c# 4', right? If you have to jump out of the CLR to get a zombie thread, it doesn't seem like a .Net problem.Bouldin
@Gambier - so, that was almost reassuring, but now I'm beginning to think there actually was something to what my teammate was saying. Here's what I'm thinking: (1) our app is on ASP.NET; (2) ASP.NET has quite a few different possible cases where it throws ThreadAbortException, and the execution paths that might lead to that happen on a per-request-basis; (3) under extremely heavy load, there are a lot of requests (by definition);Stonewort
(4) if there's also a few places where locking occurs on a per-request basis, then the probability that one thread in the thread pool is exiting the lock at the same time that another thread is doing some throwing a ThreadAbortException from something like a Response.End() is not so small as to be insignificant. What do you think?Stonewort
@Stonewort The ThreadAbortExceptions that ASP.Net throws are are thrown by the current thread, so I think the race condition while entering a lock that Eric Lippert is talking about isn't applicable (and also can't happen if the code is compiled with optimizations anyway). Its only if another thread causes the ThreadAbortException to be raised that the exception could be thrown in the no-op before the tryGambier
@Gambier - Not sure I'm following. In ASP.NET you have thread pools, so let's say there are 10 threads (thread#1, thread#2...thread#10). let's say at the same time thread#3 is about to enter a locked context to load some expensive query with a Lazy<List<Whatever>> and right before (in that no-op space) thread#7 calls Response.End(). Why wouldn't that qualify?Stonewort
@Rosales I would say almost definitely not. I've done what you described plenty of times. The Excel processes are not zombies as they are still running though not immediately accessible via the normal UI. You should be able to retrieve them via calls to GetObject. Set excelInstance = GetObject(, "Excel.Application")Formosa
@Stonewort Because Response.End would only be aborting thread#7 (the current thread that Response.End is called on). thread#3 will continue running (and enter the locked context). If thread#3 is being interrupted then the only thing I can think of would be tha there might be something in your codebase which is calling abort on other threads when a response is ended.Gambier
Hmm... why the lack of the using statement or discussing when it should be used?Subdued
"the handle to "test.txt" is still closed as soon as Abort() is called, as part of the finalizer for file..." - But the finalizer for file isn't guaranteed to be called during Abort(), or at all for that matter...Keenankeene
"The resulting zombie thread will never release the handle to the "test.txt" file and so the file will remain open until the program terminates" is incorrect. Small proof: ` static void Main(string[] args) { new Thread(GcCollect).Start(); new Thread(Target).Start(); Console.ReadLine(); } private static void Target() { using (var file = File.Open("test.txt", FileMode.OpenOrCreate)) { ExitThread(0); } } private static void GcCollect() { while (true) { Thread.Sleep(10000); GC.Collect(); } }`Cyclopedia
@Gambier - I think I see what you're saying. I think we interpreted this line from Lippert's article differently: "it is possible for another thread to cause a thread abort exception while the thread that just took the lock is in the no-op". I took it to mean that something caused another executing thread to throw a ThreadAbortException for some unrelated reason and that this would somehow cause the thread that took the lock to never release the lock.Stonewort
@Justin, The way I believe you interpreted it is that the second thread would half a reference to the thread that took the lock and would call thread.Abort() on the thread with the lock while the thread with the lock was in no-op. This makes a whole lot more sense now that I think about it. And, it seems consistent with both the rest of the article and your answers to my questions about ASP.NET. Let me know if we're finally on the same page.Stonewort
I'm going to go out on a limb and say - good on you for trying, but you still failed to create a zombie thread, because it's not possible. If you took a memory dump from your example(s) and looked at the running threads you would find the thread was not there; you would find the thread object in the heap because a live reference is still active due to the gcroot in your main(), but the OS thread would be gone. Like all other answers here - you're failing to mention the simple truth that this conceptually doesn't make sense and can't happen in .NET.Boudicca
@Stonewort Since the ASP.NET thread pool is recycled when the app is "idle" (no HTTP requests for ~20 min or so), which Abort()s all threads, I'm now worried that you can potentially run into a lot of issues with this when doing things (without a HTTP request) in a background task...Peaceful
Problem with that explanation: resources such as file handles belong to the process, not the thread (I can't think of any that belong to a thread and aren't released automatically when the thread terminates). So if "zombie thread" is to say: that thread was supposed to release a resource, but didn't, so the resource is still there, then you don't have a zombie thread, you have a resource leak.Lustick
@Gambier The url to "Locks and exceptions do not mix" is no longer valid.Dialyse
L
47

I've cleaned up my answer a bit, but left the original one below for reference

It’s the first time I've heard of the term zombies so I'll assume its definition is:

A thread that has terminated without releasing all of its resources

So given that definition, then yes, you can do that in .NET, as with other languages (C/C++, java).

However, I do not think this as a good reason not to write threaded, mission critical code in .NET. There may be other reasons to decide against .NET but writing off .NET just because you can have zombie threads somehow doesn't make sense to me. Zombie threads are possible in C/C++ (I'd even argue that it’s a lot easier to mess up in C) and a lot of critical, threaded apps are in C/C++ (high volume trading, databases etc).

Conclusion If you are in the process of deciding on a language to use, then I suggest you take the big picture into consideration: performance, team skills, schedule, integration with existing apps etc. Sure, zombie threads are something that you should think about, but since it’s so difficult to actually make this mistake in .NET compared to other languages like C, I think this concern will be overshadowed by other things like the ones mentioned above. Good luck!

Original Answer Zombies can exist if you don't write proper threading code. The same is true for other languages like C/C++ and Java. But this is not a reason not to write threaded code in .NET.

And just like with any other language, know the price before using something. It also helps to know what is happening under the hood so you can foresee any potential problems.

Reliable code for mission critical systems is not easy to write, whatever language you're in. But I'm positive it’s not impossible to do correctly in .NET. Also AFAIK, .NET threading is not that different from threading in C/C++, it uses (or is built from) the same system calls except for some .net specific constructs (like the light weight versions of RWL and event classes).

first time I've heard of the term zombies but based on your description, your colleague probably meant a thread that terminated without release all resources. This could potentially cause a deadlock, memory leak or some other bad side effect. This is obviously not desirable but singling out .NET because of this possibility is probably not a good idea since it’s possible in other languages too. I'd even argue that it’s easier to mess up in C/C++ than in .NET (especially so in C where you don't have RAII) but a lot of critical apps are written in C/C++ right? So it really depends on your individual circumstances. If you want to extract every ounce of speed from your application and want to get as close to bare metal as possible, then .NET might not be the best solution. If you are on a tight budget and do a lot of interfacing with web services/existing .net libraries/etc then .NET may be a good choice.

Laevorotation answered 19/11, 2013 at 8:49 Comment(8)
(1) I'm not sure how to figure out what's happening under the hood when I hit dead end extern methods. If you've got a suggestion, I'd love to hear it. (2) I would agree it's possible to do in .NET. I would like to believe it's possible with locking, but I have yet to find a satisfying answer to justify that todayStonewort
@Stonewort if what you mean by locking is the lock keyword, then probably not since it serializes execution. To maximize throughput, you'd have to use the right constructs depending on the characteristics of your code. I'd say if you can write reliable code in c/c++/java/whatever using pthreads/boost/thread pools/whatever, then you can write it in C# too. But if you can't write reliable code in any language using any library, then I doubt writing in C# will be any different.Laevorotation
@Stonewort as for figuring out what's under the hood, google helps heaps and if you're issue is too exotic to find on the web, reflector is very handy. But for the threading classes, I find the MSDN documentation very useful. And a lot of it are just wrappers for the same system calls you use in C anway.Laevorotation
Jerahmeel, (1) If you can find something on Google that makes this look like a LMGTFY question, I will PayPal you $100 immediately. Otherwise, please be fair. (2) You can't use reflector on extern methods. (3) I don't know C. Can you recommend a good book/resource targeting strong programmers who haven't worked with C before?Stonewort
@Stonewort not at all, that's not what I meant. I'm sorry if it came across that way. What I wanted to say is that you shouldn't be too quick to write off .NET when writing a critical application. Sure, you could do stuff that maybe a lot worse than zombie threads (which I think are just threads that didn't release any unmanaged resources, which can totally happen in other languages: #14268580), but again, that doesn't mean that .NET is not a viable solution.Laevorotation
I don't think .NET is a bad technology at all. It's actually my primary development framework. But, because of this, I think it's important that I understand the flaws to which it's susceptible. Basically, I am singling out .NET, but because I like it, not because I don't. (And no worries bro, I +1-ed you)Stonewort
It is a reason not to write threaded code in .NET, but not a reason not to use .NET. Instead of threaded code and explicit locks, use async Tasks, which also gives overlapping and parallelism but in a way that's much easier to reason about and get correct. Or any other message-passing architecture. If any piece of data is owned by only one task at a time, there's no need for locking (except in the task queuing code, which you don't have to write).Primogenial
Certainly. In CPU intensive code, multi-threaded applications at the very least free up the UI thread to maintain responsiveness to the user and allow the user to perform multiple concorrent tasks.Shreveport
L
27

Right now most of my answer has been corrected by the comments below. I won't delete the answer because I need the reputation points because the information in the comments may be valuable to readers.

Immortal Blue pointed out that in .NET 2.0 and up finally blocks are immune to thread aborts. And as commented by Andreas Niedermair, this may not be an actual zombie thread, but the following example shows how aborting a thread can cause problems:

class Program
{
    static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(Zombie));
        thread.Start();
        Thread.Sleep(500);
        thread.Abort();

        Monitor.Enter(_lock);
        Console.WriteLine("Main entered");
        Console.ReadKey();
    }

    static void Zombie()
    {
        Monitor.Enter(_lock);
        Console.WriteLine("Zombie entered");
        Thread.Sleep(1000);
        Monitor.Exit(_lock);
        Console.WriteLine("Zombie exited");
    }
}

However when using a lock() { } block, the finally would still be executed when a ThreadAbortException is fired that way.

The following information, as it turns out, is only valid for .NET 1 and .NET 1.1:

If inside the lock() { } block an other exception occurs, and the ThreadAbortException arrives exactly when the finally block is about to be ran, the lock is not released. As you mentioned, the lock() { } block is compiled as:

finally 
{
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

If another thread calls Thread.Abort() inside the generated finally block, the lock may not be released.

Leftwards answered 19/11, 2013 at 7:53 Comment(14)
you are talking about lock() but i cannot see any usage of this ... so - how is this connected? this is a "wrong" usage of Monitor.Enter and Monitor.Exit (lacking the usage of try and finally)Fisch
@AndreasNiedermair the example should answer question 2 and 3, I'll update the answer.Leftwards
I would not call that a zombie-thread - this is simply a wrong usage of Monitor.Enter and Monitor.Exit without proper usage of try and finally - anyway, your scenario will lock other threads which might hang on to _lock, so there's a deadlock-scenario - not necessarily a zombie thread ... Also, you are not releasing the lock in Main ... but, hey ... maybe the OP is locking for deadlocking instead of zombie-threads :)Fisch
@AndreasNiedermair perhaps the definition of a zombie thread isn't quite what I thought. Perhaps we can call this a "thread that has terminated execution but has not released all resources". Tempted to delete & do my homework, but keeping the answer for the resemblance to the OP's scenario.Leftwards
no offence here! actually your answer just got me thinking about it :) as the OP is explicitely talking about locks not being released, i believe his co-mate talked about dead-locking - because a lock is no real-resource which is bound to a thread (it's shared... nöna) - and therefore any "zombie" thread could be avoided by sticking to best-practices and using lock or proper try/finally-usageFisch
@AndreasNiedermair that, and avoiding Thread.Abort(). No offense taken; I went with the OP's definition of zombie without backing it up.Leftwards
i must correct ... starvation :) (en.wikipedia.org/wiki/Resource_starvation)Fisch
@Leftwards - I'm not sure that my definition is accurate. Part of why I'm asking the question is to clear this up. I think the concept is referred to heavily in C/C++Stonewort
@Leftwards - I'm having a little trouble following exactly what you mean when you're talking about the exception getting thrown inside the lock. The general idea is clear, but I'm not necessarily getting it in context of the other code you wrote, nor am I totally sure if you're referring to an actual lock statement, or to the Monitor.Enter / Monitor.Exit code that you wrote. I'm thinking you meant the lock statement, because of the note about finally but please confirm.Stonewort
@Stonewort see my updated answer. I was indeed talking about talking about the possibility of the finally block not being executed.Leftwards
@Leftwards finally blocks are immune from thread abort exceptions - see social.msdn.microsoft.com/Forums/en-US/…Geisel
@ImmortalBlue thanks for pointing that out. My information is very outdated: "In the .NET Framework versions 1.0 and 1.1, there is a chance the thread could abort while a finally block is running, in which case the finally block is aborted." msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.110).aspxLeftwards
Instead of leaving an incorrect answer unchanged due valuable information in the comments, please consider updating/correcting it instead. Despite having look at the comments note at the start, the rest of your answer has much higher visibility than the comments do. Alternately, if the information is available in other answers keeping a second copy of it here isn't needed.Vivian
The fact that a lock would get released by a thread abort is often not a good thing. Suppose an object has two fields that need to be updated consistently, and Zombie acquires a lock, updates one field, updates the other field, and then releases the lock. Should another thread be able to acquire the lock while the fields hold inconsistent values?Caldwell
B
24

This isn't about Zombie threads, but the book Effective C# has a section on implementing IDisposable, (item 17), which talks about Zombie objects which I thought you may find interesting.

I recommend reading the book itself, but the gist of it is that if you have a class either implementing IDisposable, or containing a Desctructor, the only thing you should be doing in either is releasing resources. If you do other things here, then there is a chance that the object will not be garbage collected, but will also not be accessible in any way.

It gives an example similar to below:

internal class Zombie
{
    private static readonly List<Zombie> _undead = new List<Zombie>();

    ~Zombie()
    {
        _undead.Add(this);
    }
}

When the destructor on this object is called, a reference to itself is placed on the global list, meaning it stays alive and in memory for the life of the program, but isn't accessible. This may mean that resources (particularly unmanaged resources) may not be fully released, which can cause all sorts of potential issues.

A more complete example is below. By the time the foreach loop is reached, you have 150 objects in the Undead list each containing an image, but the image has been GC'd and you get an exception if you try to use it. In this example, I am getting an ArgumentException (Parameter is not valid) when I try and do anything with the image, whether I try to save it, or even view dimensions such as height and width:

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 150; i++)
        {
            CreateImage();
        }

        GC.Collect();

        //Something to do while the GC runs
        FindPrimeNumber(1000000);

        foreach (var zombie in Zombie.Undead)
        {
            //object is still accessable, image isn't
            zombie.Image.Save(@"C:\temp\x.png");
        }

        Console.ReadLine();
    }

    //Borrowed from here
    //https://mcmap.net/q/87472/-i-need-a-slow-c-function
    public static long FindPrimeNumber(int n)
    {
        int count = 0;
        long a = 2;
        while (count < n)
        {
            long b = 2;
            int prime = 1;// to check if found a prime
            while (b * b <= a)
            {
                if (a % b == 0)
                {
                    prime = 0;
                    break;
                }
                b++;
            }
            if (prime > 0)
                count++;
            a++;
        }
        return (--a);
    }

    private static void CreateImage()
    {
        var zombie = new Zombie(new Bitmap(@"C:\temp\a.png"));
        zombie.Image.Save(@"C:\temp\b.png");
    }
}

internal class Zombie
{
    public static readonly List<Zombie> Undead = new List<Zombie>();

    public Zombie(Image image)
    {
        Image = image;
    }

    public Image Image { get; private set; }

    ~Zombie()
    {
        Undead.Add(this);
    }
}

Again, I am aware you were asking about zombie threads in particular, but the question title is about zombies in .net, and I was reminded of this and thought others may find it interesting!

Byway answered 19/11, 2013 at 9:38 Comment(7)
This is interesting. Is _undead meant to be static?Stonewort
So I tried it, with some printing out of the "destructed" object. It treats it like a normal object. Is there anything problematic about doing this?Stonewort
I've updated my answer with an example which hopefully demonstrates the issue a bit more clearly.Byway
I don't know why you got downvoted. I found it helpful. Here's a question - what kind of exception are you going to get? I don't expect it would be a NullReferenceException, because I get the feeling the missing thing needs to be more tied to the machine than to the application. Is this right?Stonewort
Well thanks very much, added the exception I get to the answer, although I don't fully understand why I get that particular exception!Byway
Surely it's not so much IDisposable that's the issue. I get the concern with finalizers (destructors) but just having IDisposable won't make an object go to the finalizer queue and risk this zombie scenario. This warning concerns finalizers, and they might call Dispose methods. There are examples where IDisposable is used in types without finalizers. The sentiment should be for resource clean-up, but that could be non-trivial resource clean-up. RX validly uses IDisposable for clean up of subscriptions and can call other downstream resources. (I didn't downvote either btw...)Wrote
The problem with doing anything but releasing native resources in a finalizer is that any referenced objects may have already been finalized themselves. Avoiding this would require searching all "unreachable"(from any "root") objects for references to them from any other "unreachable" objects, and there might be cycles anyway. This applies even inside a finalizer, not just if you try to "resurrect" an object by adding a new reference to it; though doing so helps demonstrate "loosing" the race condition. Hmm, 6 years later so this is necromancy on a thread about zombies.Whitener
W
21

On critical systems under heavy load, writing lock-free code is better primarily because of the performance improvments. Look at stuff like LMAX and how it leverages "mechanical sympathy" for great discussions of this. Worry about zombie threads though? I think that's an edge case that's just a bug to be ironed out, and not a good enough reason not to use lock.

Sounds more like your friend is just being fancy and flaunting his knowledege of obscure exotic terminology to me! In all the time I was running the performance labs at Microsoft UK, I never came across an instance of this issue in .NET.

Wrote answered 19/11, 2013 at 8:24 Comment(7)
I think different sets of experiences make us paranoid about different bugs. He acknowledged it's an extreme edge case while explaining it, but if it really is an edge-case and not a never-case, I'd like to at least understand it a little better - Thanks for your inputStonewort
Fair enough. I just wouldn't want you to be disproportionately worried about the lock statement!Wrote
I'd be much less worried if I had a deeper understanding of this issue.Stonewort
I upvoted because you are trying to get rid of some thread-FUD, of which there is far too much. Lock-FUD is going to take more work to dispose of:) I just cringe when devs take an unbounded spinlock, (for performance), so that they can copy 50K of data onto a wide queue.Permissible
By performance improvements, do you mean the actual cost of locking? Also, are you speaking about locks in .NET or locks in general?Stonewort
Yes, in both the general and particular. Writing correct lock-free code is about as challenging as programming gets. In my experience for the vast majority of cases, big fat course-grained (relatively) easy to comprehend lock blocks are just fine. I would at avoid introducing complexity for the sake of performance optimization until you know you need to.Wrote
+100 if I could. Spot on about correct lock-free code, I've had to optimize some really critical sections of code before for this particular purpose in high-traffic IIS systems; I can think of nothing more complex or strange I've done. On the plus side, that experience taught me a significant amount about how multithreading and resources work in .NET to which I say: Bravo for being the only sane voice here. Every other answer here is really stretching and confusing issues because these are not well known/studied things. People need to read Tess Fernandez' blog or avoid commenting on GC.Boudicca
S
3

1.Is there a clearer definition of a "zombie thread" than what I've explained here?

I do agree that "Zombie Threads" exist, it's a term to refer to what happens with Threads that are left with resources that they don't let go of and yet don't completely die, hence the name "zombie," so your explanation of this referral is pretty right on the money!

2.Can zombie threads occur on .NET? (Why/Why not?)

Yes they can occur. It's a reference, and actually referred to by Windows as "zombie": MSDN uses the Word "Zombie" for Dead processes/threads

Happening frequently it's another story, and depends on your coding techniques and practices, as for you that like Thread Locking and have done it for a while I wouldn't even worry about that scenario happening to you.

And Yes, as @KevinPanko correctly mentioned in the comments, "Zombie Threads" do come from Unix which is why they are used in XCode-ObjectiveC and referred to as "NSZombie" and used for debugging. It behaves pretty much the same way... the only difference is an object that should've died becomes a "ZombieObject" for debugging instead of the "Zombie Thread" which might be a potential problem in your code.

Sturgill answered 19/11, 2013 at 16:36 Comment(4)
But the way MSDN uses zombie is very different from the way this question is using it.Primogenial
Oh yes I agree, but I was making a point that even MSDN refers to threads as Zombie Threads when dead. and that they in fact can occur.Sturgill
Sure, but it's referring to some other code still holding a handle to the thread, not the thread holding handles to resources when it exited. Your first sentence, agreeing with the definition in the question, is what's wrong.Primogenial
Ahh I see your point. To be honest I didn't even notice that. I was focusing to his point that the definition exists, regardless of how it happens. Remember that the definition exist because of what happens to the thread, and not how it is done.Sturgill
T
0

I can make zombie threads easily enough.

var zombies = new List<Thread>();
while(true)
{
    var th = new Thread(()=>{});
    th.Start();
    zombies.Add(th);
}

This leaks the thread handles (for Join()). It's just another memory leak as far as we are concerned in the managed world.

Now then, killing a thread in a way that it actually holds locks is a pain in the rear but possible. The other guy's ExitThread() does the job. As he found, the file handle got cleaned up by the gc but a lock around an object wouldn't. But why would you do that?

Thorvaldsen answered 14/11, 2017 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.