diff --git a/src/generators.h b/src/generators.h index 514dc4069c..f70d599b9f 100644 --- a/src/generators.h +++ b/src/generators.h @@ -151,8 +151,14 @@ struct OrtGlobals { std::unique_ptr env_; struct Allocator { - std::unique_ptr allocator_; + // Field order matters here. The OrtAllocator returned by OrtApi::CreateAllocator (called via + // Ort::Allocator::Create) "wraps the internal allocator from the OrtSession and becomes invalid when the session + // does" -- see + // https://github.com/microsoft/onnxruntime/blob/3c8c46029735a89c8d1ea0aa6c1812db5b78ad72/include/onnxruntime/core/session/onnxruntime_c_api.h#L2852-L2862 + // Members are destroyed in reverse declaration order, so session_ must be declared BEFORE allocator_ so that + // ~allocator_ runs first. std::unique_ptr session_; + std::unique_ptr allocator_; }; Allocator device_allocators_[static_cast(DeviceType::MAX)]; diff --git a/src/models/onnxruntime_api.h b/src/models/onnxruntime_api.h index 55d71af55a..ff727f11b6 100644 --- a/src/models/onnxruntime_api.h +++ b/src/models/onnxruntime_api.h @@ -426,6 +426,9 @@ struct Abstract { */ struct Allocator : OrtAllocator { static Allocator& GetWithDefaultOptions(); ///< ::OrtAllocator default instance that is owned by Onnxruntime + /// Wraps the internal allocator owned by `session` (forwards to OrtApi::CreateAllocator). The returned Allocator + /// becomes invalid when `session` is destroyed -- callers must ensure the Allocator is destroyed before the + /// OrtSession. static std::unique_ptr Create(const OrtSession& session, const OrtMemoryInfo& memory_info); void* Alloc(size_t size); diff --git a/src/ort_genai.h b/src/ort_genai.h index b2e7cd43c7..15d9af9d0a 100644 --- a/src/ort_genai.h +++ b/src/ort_genai.h @@ -847,6 +847,18 @@ struct OgaEngine : OgaAbstract { static void operator delete(void* p) { OgaDestroyEngine(reinterpret_cast(p)); } }; +/** + * \brief RAII wrapper that calls OgaShutdown() on destruction. + * + * \warning Without explicit shutdown, GenAI's globals are destroyed at static-destruction time in undefined order, + * which may crash. + * + * \note Typical usage is to construct an instance early in the program so its destructor runs before process exit. + * + * \note Only one OgaHandle should be live in the process, and its scope must encompass all GenAI use. GenAI's globals + * are not re-creatable after OgaShutdown(); a second OgaHandle whose lifetime starts after the first one's + * destruction would leave subsequent GenAI calls broken. + */ struct OgaHandle { OgaHandle() = default; ~OgaHandle() noexcept { diff --git a/src/ort_genai_c.h b/src/ort_genai_c.h index f79c6ef42e..c553caa17c 100644 --- a/src/ort_genai_c.h +++ b/src/ort_genai_c.h @@ -88,7 +88,16 @@ typedef struct OgaStreamingProcessor OgaStreamingProcessor; */ /** - * \brief Call this on process exit to cleanly shutdown the genai library & its onnxruntime usage + * \brief Shuts down the GenAI library and releases all GenAI-owned ONNX Runtime globals. + * + * \warning Callers SHOULD invoke OgaShutdown() before process exit. If it is not called, GenAI's globals are destroyed + * at static-destruction time in undefined order, which may crash. + * + * \note C++ callers should prefer the OgaHandle RAII wrapper in ort_genai.h; C# callers should prefer the + * OgaHandle IDisposable wrapper. Both invoke OgaShutdown() on destruction. + * + * \note Must be the last GenAI call in the process. Any OgaModel / OgaGenerator / OgaTokenizer / etc. handles owned + * by the caller must be released first. */ OGA_EXPORT void OGA_API_CALL OgaShutdown();