Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ThreadLocals of the same type T can interfere with each other's trackAllValues #55796

Closed
jkotas opened this issue Jul 16, 2021 · 4 comments · Fixed by #56956
Closed

ThreadLocals of the same type T can interfere with each other's trackAllValues #55796

jkotas opened this issue Jul 16, 2021 · 4 comments · Fixed by #56956

Comments

@jkotas
Copy link
Member

jkotas commented Jul 16, 2021

Repro:

Problem 1:
Compile and run the program below
Actual result: 100
Expected result: 0. The values associated with collected threads should not be reported.

Problem 2:
Uncomment UNRELATED_THREAD_LOCAL, compile and run the program below
Actual result: 0
Expected result: same as with UNRELATED_THREAD_LOCAL commented out. The two unrelated ThreadLocal instances should not influence each other

// #define UNRELATED_THREAD_LOCAL

using System;
using System.Threading;

class Program
{
#if UNRELATED_THREAD_LOCAL
    static ThreadLocal<bool> s_unrelatedThreadLocal;
#endif
    static ThreadLocal<bool> s_threadLocal;

    static void Work()
    {
#if UNRELATED_THREAD_LOCAL
        s_unrelatedThreadLocal.Value = true;
#endif
        s_threadLocal.Value = true;
    }

    static void Main(string[] args)
    {
#if UNRELATED_THREAD_LOCAL
        s_unrelatedThreadLocal = new ThreadLocal<bool>(false);
#endif
        s_threadLocal = new ThreadLocal<bool>(true);
        for (int i = 0; i < 100; i++)
        {
            Thread t = new Thread(Work);
            t.Start();
            t.Join();
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        int count = 0;
        foreach (var x in s_threadLocal.Values) { if (x) count++; }
        Console.WriteLine(count);
    }
}
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Threading untriaged New issue has not been triaged by the area owner labels Jul 16, 2021
@ghost
Copy link

ghost commented Jul 16, 2021

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

Issue Details

Repro:

Problem 1:
Compile and run the program below
Actual result: 100
Expected result: 0. The values associated with collected threads should not be reported.

Problem 2:
Uncomment UNRELATED_THREAD_LOCAL, compile and run the program below
Actual result: 0
Expected result: same as with UNRELATED_THREAD_LOCAL commented out. The two unrelated ThreadLocal instances should not influence each other

// #define UNRELATED_THREAD_LOCAL

using System;
using System.Threading;

class Program
{
#if UNRELATED_THREAD_LOCAL
    static ThreadLocal<bool> s_unrelatedThreadLocal;
#endif
    static ThreadLocal<bool> s_threadLocal;

    static void Work()
    {
#if UNRELATED_THREAD_LOCAL
        s_unrelatedThreadLocal.Value = true;
#endif
        s_threadLocal.Value = true;
    }

    static void Main(string[] args)
    {
#if UNRELATED_THREAD_LOCAL
        s_unrelatedThreadLocal = new ThreadLocal<bool>(false);
#endif
        s_threadLocal = new ThreadLocal<bool>(true);
        for (int i = 0; i < 100; i++)
        {
            Thread t = new Thread(Work);
            t.Start();
            t.Join();
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        int count = 0;
        foreach (var x in s_threadLocal.Values) { if (x) count++; }
        Console.WriteLine(count);
    }
}
Author: jkotas
Assignees: -
Labels:

area-System.Threading, untriaged

Milestone: -

@jkotas jkotas added the bug label Jul 16, 2021
@stephentoub
Copy link
Member

Actual result: 100
Expected result: 0. The values associated with collected threads should not be reported.

Why would the expected result be 0? The purpose of trackAllValues:true is that all values are kept, regardless of whether the thread is still alive. Or, at least, that was the intention, even if the docs are confusing on the topic. The idea was to be able to use it for things like parallel aggregations or parallel storage of resources that should be cleaned up; the main host would spawn all of the operations, they'd all go off and do their work on whatever threads, and then the host could query Values after the fact and collect/dispose of all of the values or resources, without concern for whether the underlying threads were still around.

So, the actual result of 100 is what I'd expect.

Uncomment UNRELATED_THREAD_LOCAL, compile and run the program below
Actual result: 0
Expected result: same as with UNRELATED_THREAD_LOCAL commented out. The two unrelated ThreadLocal instances should not influence each other

This, however, does appear to be a bug.

@stephentoub stephentoub changed the title ThreadLocal(trackAllValues: true) does not work ThreadLocals of the same type T can interfere with each other's trackAllValues Jul 16, 2021
@jkotas
Copy link
Member Author

jkotas commented Jul 16, 2021

The purpose of trackAllValues:true is that all values are kept, regardless of whether the thread is still alive. Or, at least, that was the intention, even if the docs are confusing on the topic

Yes, the docs are confusing. It is not what I would expect the behavior to be by reading the docs.

@stephentoub
Copy link
Member

the docs are confusing

Agreed. I'll submit a PR to fix them.

@stephentoub stephentoub removed the untriaged New issue has not been triaged by the area owner label Jul 17, 2021
@stephentoub stephentoub added this to the 6.0.0 milestone Jul 17, 2021
davidwrighton added a commit to davidwrighton/runtime that referenced this issue Aug 6, 2021
- Before this change the trackAllValues behavior for ThreadLocal<SomeParticularType> was defined by the first instance of thread local to have its value set on the thread
  - This could lead to unpredictable memory leaks (where the value was improperly tracked even though it wasn't supposed to be) This reproduces as a memory leak with no other observable behavior
  - Or data loss, where the Values collection was missing entries.
- Change the model so that ThreadLocal<T> trackAllValues behavior is properly defined by the exact ThreadLocal<T> instance in use
- Implement by keeping track of the track all changes behavior within the IdManager

Fixes dotnet#55796
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 6, 2021
davidwrighton added a commit that referenced this issue Aug 10, 2021
- Before this change the trackAllValues behavior for ThreadLocal<SomeParticularType> was defined by the first instance of thread local to have its value set on the thread
  - This could lead to unpredictable memory leaks (where the value was improperly tracked even though it wasn't supposed to be) This reproduces as a memory leak with no other observable behavior
  - Or data loss, where the Values collection was missing entries.
- Change the model so that ThreadLocal<T> trackAllValues behavior is properly defined by the exact ThreadLocal<T> instance in use
- Implement by keeping track of the track all changes behavior within the IdManager

Fixes #55796
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 10, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Sep 9, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants