Opened 5 years ago

Last modified 5 years ago

#13008 new Bugs

[windows][Visual Studio compiler] Building Boost Thread with /GL causes leak in boost::thread_specific_ptr

Reported by: daniel.kruegler@… Owned by: Anthony Williams
Milestone: To Be Determined Component: thread
Version: Boost 1.64.0 Severity: Optimization
Keywords: Cc:

Description

When Boost Thread is build using the Visual Studio compiler with the optimization flag /GL ("Whole Program Optimization") added, and when a program links statically against Boost Thread for release builds, instances of boost::thread_specific_ptr don't call the cleanup function for object instances in other threads when threads have been started via windows API (not using boost::thread). The problem occurs for both 32-bit and 64-bit systems, and has been observed on Windows 7 and Windows 10, and for both auto-linking and not auto-linking situations.

How to reproduce:

a) Build the relevant Boost libraries using the following command:

b2 --build-dir="%TMP%" toolset=msvc-14.1 --with-thread --with-system --with-date_time --with-atomic address-model=32 link=static,shared variant=release asynch-exceptions=on extern-c-nothrow=off rtti=on optimization=speed cxxflags="/GL" linkflags="/LTCG:incremental" --stagedir=C:\SomePath --compiler="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\bin\HostX64\x64\cl.exe"

b) Build statically against the following translation unit in release mode and run the program:

#include <iostream>
#include "boost/config.hpp"
#include "boost/thread/tss.hpp"
#include "boost/atomic.hpp"

#if !defined(BOOST_HAS_WINTHREADS)
#  error Windows platform required
#endif

#include <windows.h>

// Uncomment to activate IO output for debugging purposes:
#define BDAL_USE_IO_OUTPUT

typedef int atomic_int_underlying_type;
typedef boost::atomic<bool> atomic_bool_type;
typedef boost::atomic<atomic_int_underlying_type> atomic_integral;

atomic_integral instance_counter(0);
atomic_bool_type test_result(false);

struct InstanceCountingClass
{
  InstanceCountingClass()
  {
    ++instance_counter;
#ifdef BDAL_USE_IO_OUTPUT
    std::cout << "[InstanceCountingClass] default c'tor" << std::endl;
#endif
  }
  InstanceCountingClass(const InstanceCountingClass&)
  {
    ++instance_counter;
#ifdef BDAL_USE_IO_OUTPUT
    std::cout << "[InstanceCountingClass] copy c'tor" << std::endl;
#endif
  }
  ~InstanceCountingClass()
  {
    --instance_counter;
#ifdef BDAL_USE_IO_OUTPUT
    std::cout << "[InstanceCountingClass] d'tor" << std::endl;
#endif
  }
};

boost::thread_specific_ptr<InstanceCountingClass> tss;

DWORD WINAPI myThreadFunction(LPVOID)
{
#ifdef BDAL_USE_IO_OUTPUT
  std::cout << "[myThreadFunction] called" << std::endl;
#endif
  atomic_int_underlying_type cnt = instance_counter.load();
  if (cnt != 1) {
    std::cerr << "[myThreadFunction] TSS instance counter is different from 1: " << cnt << std::endl;
    test_result.store(false);
  }
  if (tss.get()) {
    std::cerr << "[myThreadFunction] TSS contains unexpected value" << std::endl;
    test_result.store(false);
  }
  tss.reset(new InstanceCountingClass());
  cnt = instance_counter.load();
  if (cnt != 2) {
    std::cerr << "[myThreadFunction] TSS instance counter is different from 2: " << cnt << std::endl;
    test_result.store(false);
  }
  return 0;
}

bool doTestBoostThreadSpecificPtrCleanup()
{
  instance_counter.store(0);
  test_result.store(true);

  if (tss.get()) {
    std::cerr << "[doTestBoostThreadSpecificPtrCleanup] TSS contains unexpected value" << std::endl;
    return false;
  }

  tss.reset(new InstanceCountingClass());

  // Use Windows API to instantiate a thread
  HANDLE threadhandle = ::CreateThread(NULL, 0, myThreadFunction, NULL, 0, 0);
  if (!threadhandle) {
    std::cerr << "[doTestBoostThreadSpecificPtrCleanup] CreateThread failed" << std::endl;
    return false;
  }

  // Wait for thread to exit
  DWORD rc = ::WaitForSingleObject(threadhandle, INFINITE);
  if (!::CloseHandle(threadhandle) || rc != WAIT_OBJECT_0 || !test_result) {
    std::cerr << "[doTestBoostThreadSpecificPtrCleanup] WaitForSingleObject or CloseHandle failed" << std::endl;
    return false;
  }

  // Make sure that the thread-specific instance has been destroyed,
  // even though we haven't used the Boost.Thread API to start the
  // thread.
  atomic_int_underlying_type cnt = instance_counter.load();
  if (cnt != 1) {
    std::cerr << "[doTestBoostThreadSpecificPtrCleanup] TSS instance counter is different from 1: "
      << cnt << std::endl;
    return false;
  }

  return true;
}

int main()
{
  if (doTestBoostThreadSpecificPtrCleanup())
  {
    std::cout << "SUCCESS!" << std::endl;
  }
  else
  {
    std::cerr << "FAILURE!" << std::endl;
  }
}

Observed output:

[InstanceCountingClass] default c'tor
[myThreadFunction] called
[InstanceCountingClass] default c'tor
[doTestBoostThreadSpecificPtrCleanup] TSS instance counter is different from 1: 2
FAILURE!
[InstanceCountingClass] d'tor

Verified for:

Boost versions: 1.64, 1.63
Visual Studio versions: VS 2012 (toolset=msvc-11.0), VS 2015 (toolset=msvc-14.0), VS 2017 (toolset=msvc-14.1)

Workaround:

Don't build Boost using the /GL compiler flag.

Change History (6)

comment:1 by viboes, 5 years ago

Thanks for the report, but I don't know how to fix this. thread_specific_ptr has too much linker issues.

comment:2 by daniel.kruegler@…, 5 years ago

This was mainly a documentary issue because we needed quite a while to figure out *what* was the actual cause of a problem that we had after we had build Boost with this setting, since the problem manifested very late and at a completely unexpected point. My main motivation was to give other Boost users a chance to read that issue when they also were investigating a similar issue. I would not be opposed if you would wish to close the issue as WON'T FIX based on your arguments.

comment:3 by viboes, 5 years ago

Could I ask you to propose a documentation patch?

comment:4 by anonymous, 5 years ago

Good idea. I will take a look at it, but presumably I need at least a week from now on for working on this.

in reply to:  3 comment:5 by daniel.kruegler@…, 5 years ago

Replying to viboes:

Could I ask you to propose a documentation patch?

Could you give me a pointer for the proper location for that patch? Initially I thought that the chapter "Limitations" would be the correct place for it, but it seems that it lists only limitations of boost features that are macros starting with "BOOST_THREAD_"

There is also the chapter "Using and building the library", but I also find only examples of the effects or validity to define BOOST_ macros.

I'm now confused where to put such an information, any hints are appreciated.

comment:6 by viboes, 5 years ago

Sorry for the late response. Maybe you could add them to the thread_specific_ptr section.

Note: See TracTickets for help on using tickets.