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

ICU-11276 Using atomics for thread safety in NumberRangeFormatter. #130

Merged
merged 2 commits into from
Sep 21, 2018

Conversation

sffc
Copy link
Member

@sffc sffc commented Sep 17, 2018

I need to use some form of an atomic for proper thread safety. Here is one way to do it, my preferred option, which involves including in a public header file. If this is problematic, I can work around by newing an internal object for the atomic, which comes at the cost of extra mallocs.

Checklist

@@ -5,6 +5,7 @@
#ifndef __NUMBERRANGEFORMATTER_H__
#define __NUMBERRANGEFORMATTER_H__

#include <atomic>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we can include STL headers in the public ICU headers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good question. I'm not sure of the answer. I'm a little wary too.

It would be nice to not have the dependency from the public ICU header, but getting rid of it would require some trickiness on the implementation side. I'd like to hear what everyone else thinks. The header should be standard, and the functions we need don't throw exceptions, or allocate memory, or do other dicey things - they should pretty much inline to simple machine instructions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I need the import here is because I need the type information.

Whether or not I #import in the public header file, I'm going to be using it internally for the implementation, something we already do elsewhere in ICU (for example, in decimfmt.cpp).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a concern at one point that putting this in the public header means that ICU and the client have to both be compiled with the same version of STL?

Like, if the definition of std::atomic changes between C++ compiler implementations, we still want ICU to work. Maybe std::atomic is stable and this isn't actually a problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our public ICU headers, dragging in somewhat arcane system headers that are unrelated to our public, user-accessible API just doesn't seem desirable.

I'm guessing we could probably get away with it, but it would be nicer to avoid it. And there's some increased risk of platform problems. Although I can't immediately think of why there should be, problems have a way of happening anyhow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from asking some folks internally, their opinion was that using std::atomic is in the same category (or just as bad) as using something like std::vector or std::string in a public header.

Their reasoning is that there can be (and have been) ABI breaking changes between library versions. For example the sizeof(std::atomic<T*>) can vary depending on the version of the library that you are building/compiling with.

This is still a problem even for private members, as the sizeof the entire class depends on the particular implementation details of std::atomic. This means that the size of the class can change if the library version changes.

So if anybody downloads pre-built binaries for ICU, and then builds with a different version of the STL library, things might not work and/or they would be broken. :(

(As an aside: supposedly there is some work in C++20 to try and address this... However, it feels like it will be long time time before we can depend on C++20 in ICU.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, perhaps this isn't as much of an issue as initially suggested and/or worried about?

From looking at the docs, it doesn't look like ICU4C makes any sort of claim about binary compatibility for C++ at all (only for the C API surface).

From the ICU docs: http://userguide.icu-project.org/design#TOC-ICU-Binary-Compatibility:-Using-ICU-as-an-Operating-System-Level-Library

Here are the requirements for enabling binary compatibility for ICU4C:

  • Applications must use only APIs that are marked as stable.
  • Applications must use only plain C APIs, never C++.
  • ICU must be built with function renaming disabled.
  • Applications must be built using an ICU that was configured for binary compatibility.
  • Use ICU version 3.0 or later.
    ...

C APIs only. Only plain C APIs remain compatible across ICU releases. The reason C++ binary compatibility is not supported is primarily because the design of C++ language and runtime environments present extreme technical difficulties to doing so. Stable C++ APIs are source compatible, but applications using them must be recompiled when moving between ICU releases.

If there is only source compatibility for the C++ API surface, then this might(?) be fine, as there should be no direct usage/reference to std::atomic in the C API surface.
(Though the library DLLs like icuuc and icuin would all need to be compiled with the exact same compiler/runtime/library/etc.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is only source compatibility for the C++ APIs, and the ICU library and the application must be built with run-time compatible compilers & libraries.

There should be no problem with the plain C APIs, no matter which way we go on this question.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if anybody downloads pre-built binaries for ICU, and then builds with a different version of the STL library, things might not work and/or they would be broken. :(

This seems like it's still an issue, is this addressed?

@srl295
Copy link
Member

srl295 commented Sep 18, 2018 via email

srl295
srl295 previously approved these changes Sep 20, 2018
Copy link
Member

@srl295 srl295 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM as they are, but with a suggestion for avoiding atomic in the public header.

@@ -5,6 +5,7 @@
#ifndef __NUMBERRANGEFORMATTER_H__
#define __NUMBERRANGEFORMATTER_H__

#include <atomic>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if anybody downloads pre-built binaries for ICU, and then builds with a different version of the STL library, things might not work and/or they would be broken. :(

This seems like it's still an issue, is this addressed?

@@ -612,8 +613,9 @@ class U_I18N_API LocalizedNumberRangeFormatter
~LocalizedNumberRangeFormatter();

private:
// TODO: This is not thread-safe! Do NOT check this in without an atomic here.
impl::NumberRangeFormatterImpl* fImpl = nullptr;
std::atomic<impl::NumberRangeFormatterImpl*> fAtomicFormatter = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I don't see fAtomicFormatter called from an inline environment. So I don't see a much of an argument for including this in a public header. Can the atomicity be implemented entirely within the .cpp side? Perhaps with an opaque type here in the class definition?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can. But how to you make an opaque type? We don't know how big the std::atomic needs to be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm coming to the conclusion that we should go ahead and use std::atomic directly. The alternatives all add complexity or performance hits, or both.

Headers aside, if there are runtime libraries with incompatible implementations of atomics, and ICU ends up dynamically (at load time) linked with an incompatible RT library, ICU is most likely going to fail. Because of a mixture of inline and out-of-line code. This will happen no matter what choice we make for the immediate question.

But failures are, I think, quite unlikely. The language standard encourages, but does not require, implementations to keep the sizeof (std::atomic) the same as sizeof(T). For simple pointer types, with all known CPUs having direct support, it's likely true. (Less likely to be the case for larger or more complex types)

So, my vote is to try it, and if we run into problems, do something else in a maintenance fix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aheninger

…This will happen no matter what choice we make for the immediate question

your logic is good here. 👍

@sffc sffc merged commit 95c9c90 into unicode-org:master Sep 21, 2018
@sffc sffc deleted the numberrange branch September 21, 2018 23:33
@srl295
Copy link
Member

srl295 commented Sep 22, 2018

👍

sffc added a commit to sffc/icu that referenced this pull request Sep 26, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 26, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
sffc added a commit to sffc/icu that referenced this pull request Sep 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants