Skip to content

ThreadSafeFunction memory violation in ThreadSafeFinalize #632

Closed
@averrez

Description

@averrez

Hi everyone,

I've been experiencing random rare crashes in both Mac and Windows when Napi::ThreadSafeFunction is deallocated. My investigation brought me to the New function:

template <typename ResourceString, typename ContextType,
          typename Finalizer, typename FinalizerDataType>
inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,
                                                  const Function& callback,
                                                  const Object& resource,
                                                  ResourceString resourceName,
                                                  size_t maxQueueSize,
                                                  size_t initialThreadCount,
                                                  ContextType* context,
                                                  Finalizer finalizeCallback,
                                                  FinalizerDataType* data,
                                                  napi_finalize wrapper) {
  static_assert(details::can_make_string<ResourceString>::value
      || std::is_convertible<ResourceString, napi_value>::value,
      "Resource name should be convertible to the string type");

  ThreadSafeFunction tsfn; // <---- Stack allocated.
  auto* finalizeData = new details::ThreadSafeFinalize<ContextType, Finalizer,
      FinalizerDataType>({ data, finalizeCallback, &tsfn._tsfn }); // <---- Pointer to ivar stored
  napi_status status = napi_create_threadsafe_function(env, callback, resource,
      Value::From(env, resourceName), maxQueueSize, initialThreadCount,
      finalizeData, wrapper, context, CallJS, &tsfn._tsfn);
  if (status != napi_ok) {
    delete finalizeData;
    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());
  }

  return tsfn;
}

I believe that the issue is that ThreadSafeFunction tsfn; is stack-allocated and a pointer to its ivar is stored in the ThreadSafeFinalize: { data, finalizeCallback, &tsfn._tsfn }

When function is being destroyed, the finalizer is called with this code in it:

if (finalizeData->tsfn) {
      *finalizeData->tsfn = nullptr;
}

If I understand correctly, it writes nullptr to the captured pointer, which is long gone by now.
Also, there are no other references in the code to that member. It feels like it's not used anywhere, and storing and nullifying it is redundant?

I also tested it with Xcode memory sanitizer and it confirmed that stack memory is being corrupted from ThreadSafeFinalize.

Thank you.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions