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

Deprecate Close method in FairSource/Sink #1468

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from

Conversation

YanzhaoW
Copy link

@YanzhaoW YanzhaoW commented Dec 2, 2023

  • Deprecation of Close method in FairSource/Sink.
  • Move the Close executions in the derived classes to their dtor (if needed).
  • Remove callings of the Close methods in FairRootManager and FairRun.

Checklist:

@YanzhaoW YanzhaoW changed the base branch from master to dev December 2, 2023 15:36
@YanzhaoW
Copy link
Author

YanzhaoW commented Dec 2, 2023

a sidenote:
I have rewritten the FairFileSource class in R3BRoot in this PR (actually it's R3BFileSource, which is a copy-paste from FairFileSource). Maybe we could migrate the new class here?

Copy link
Member

@ChristianTackeGSI ChristianTackeGSI left a comment

Choose a reason for hiding this comment

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

Many thanks for this PR! This is great!

First initial set of comments. I haven't looked deep enough yet.

fairroot/base/sink/FairRootFileSink.cxx Outdated Show resolved Hide resolved
fairroot/base/sink/FairSink.h Outdated Show resolved Hide resolved
fairroot/base/steer/FairRootManager.h Show resolved Hide resolved
@@ -558,7 +558,6 @@ void FairRunAna::TerminateRun()
// cout << ">>>------------------------------------------------<<<" << endl;
fRootManager->LastFill();
fRootManager->Write();
fRootManager->CloseSink();
Copy link
Member

Choose a reason for hiding this comment

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

Sadly enough this is also part of our public API (at least in my view). So we should keep calling the Close member functions in the deprecation phase. So that people can actually continue using their stuff that depends on their Sink's/Source's Close member function being called.

Maybe we should replace this with something like this at least?

    iff(fSink) {
        fSink->Close();
    }

Copy link
Author

Choose a reason for hiding this comment

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

But if we do this, we will always get deprecation warning no matter what. I think deprecation means asking people to fix something to get rid of the warning. But in this case, even they fix it and put the resource closing in the dtor, they still get this deprecation warning because the calling is guaranteed in the library code.

Copy link
Member

Choose a reason for hiding this comment

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

The deprecation warning only happens in the FairRoot "library" code while compiling it. So normal users wont be hit by it.

And we should just stop that warning with a set of

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    iff(fSink) {
        fSink->Close();
    }
#pragma GCC diagnostic pop

Once we finally remove the Close member function, we will get a nice compile error on this, and we can remove all of this.

Copy link
Member

Choose a reason for hiding this comment

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

The real problem is, that we can't throw a warning if people override Close. We can't tell them "Well, you have overriden it, so you think that this makes any difference? This behaviour will change!". And I don't know of any good solution to this, except for putting a good warning in the Release Notes (which you did!).

Copy link
Author

Choose a reason for hiding this comment

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

that we can't throw a warning if people override Close.

I think we can't even if we want. If they override the Close method and call it with the derived class type, there won't be any warning. Warning only occurs when they cast back to the base class and call the Close method.

And we should just stop that warning with a set of

I'm not sure whether it's a good idea to still enforce the closing. The point of deprecating the Close method is to tell users to deal with closings themselves. But with this closing, it kind of makes this transition harder. And in some cases where users don't want to close the resources twice, they have to give the controls of closings back to FairRootManager, which is exactly the opposite to what we want. Deprecation means something will be discarded and tell users not to use them. It's weird to see that we deprecate the method while still keep using it.

Copy link
Member

@ChristianTackeGSI ChristianTackeGSI left a comment

Choose a reason for hiding this comment

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

One other note.

Comment on lines +41 to +45
PixelDigiBinSource::~PixelDigiBinSource()
{
fInputFile.close();
}

Copy link
Member

Choose a reason for hiding this comment

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

fInputFile is a std::ifstream. So its dtor should handle that for us. :-)

Copy link
Author

Choose a reason for hiding this comment

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

ok

Copy link
Member

@ChristianTackeGSI ChristianTackeGSI left a comment

Choose a reason for hiding this comment

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

More things to keep, IMHO.

Surround them with

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    …
#pragma GCC diagnostic pop

as needed.

Comment on lines -203 to -209
void FairRootFileSink::Close()
{
if (fRootFile) {
fRootFile->Close();
}
}

Copy link
Member

Choose a reason for hiding this comment

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

But I think, this is public API still?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, without the declaration here, FairRootFileSink still have Close public API, which falls back to the implementation of its base class.

void CloseSink()
{
if (fSink) {
fSink->Close();
}
}
[[deprecated("Close will go away, the dtor closes resources")]] void CloseSink() {}
Copy link
Member

Choose a reason for hiding this comment

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

Please keep the complete old version as well here.

Copy link
Author

Choose a reason for hiding this comment

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

This could crash user's program if they implement the close in the dtor as well. Some user close implementations may not allow the second closing.

Comment on lines -318 to -320
if (fSink) {
fSink->Close();
}
Copy link
Member

Choose a reason for hiding this comment

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

This one should be kept IMHO as well.

Comment on lines -312 to -314
GetSource()->Close();

fRootManager->CloseSink();
Copy link
Member

Choose a reason for hiding this comment

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

And this one as well.

@karabowi
Copy link
Collaborator

karabowi commented Dec 7, 2023

I have tested FairRoot's private branch without FairSampler::Close() and FairSink::Close()
with PandaRoot. I did not observe any crash.

@ChristianTackeGSI
Copy link
Member

Deprecations in General

For me a deprecation means, that

  1. All old code stays as it is (as long as it can be considered public API). So we should not change too much code. So that users' code continues to work as without this PR!
  2. Any public API that should not be used in the future by users, should be marked as deprecated (either via [[deprecated]], or via comments, if the former is not possible).

In FairRoot: Public API

Let's look at (1):

Public API is basically everything in fairroot/. So we should not change any code or behaviour there, basically. Of course we can do some simple stuff: Instead of calling FairRootManager::CloseSink (which calls fSink->Close();) we can call fSink->Close(); directly. This does not change any actual behaviour.

When it comes to examples/ we should go the full route! We should show to users what we expect their code to look like in the future.

FairRoot: The confusing Close situation

In a cool world, the old situation would look like this:

class FairSourceSink {
public:
    void CloseForUsers() { CloseImpl(); }
private:
    // Please override in your implementation
    virtual void CloseImpl() = 0;
private:
    // So that the library can call private member functions
    friend FairRun;
    friend FairRootManager;
    // Will be called internally by the library as a "call back like thing"
    void CloseForInternalUse() { CloseImpl(); }
};

Let's go step by step.

  1. We want to deprecate CloseForUsers, so that users do not call it any more:

    public:
        [[deprecated]] void CloseForUsers() { CloseImpl(); }

    Note: CloseForInternalUse is not deprecated. We want to continue calling it from the library, so that the CloseImpl is still called.

    Corollary: We would remove any calls to CloseForUsers from examples/!

  2. We want to deprecate overriding CloseImpl:

    private:
        // \deprecated Please stop overriding it!
        virtual void CloseImpl() {}

    Note 1: There is no point in marking it override. We want to deprecate overriding it. No need to deprecate calling it. It can only be called by us. All of our business.

    Note 2: We still continue calling CloseForInternalUse (to keep bevahiour exactly as it is)

    Note 3: We would still keep the overriding in fairroot/, because we did not yet remove CloseForUsers(). So code that depends on CloseForUsers() calling something should be happy.

    Corollary: We would remove any overrding of CloseImpl from examples/.

  3. Remove CloseForUsers, keep everything else the same

  4. Remove CloseImpl, and remove all of CloseForInternalUse.

That's my current idea on how this should look like.

The problem is now, that all of those three (CloseImpl, CloseForInternalUse, and CloseForUsers) are currently in one function Close to make things complex.

To maybe simplify things, we could temporarily invent:

private
   void CloseForInternalUse() {
       // All of these pragmas, because we would call
       // CloseImpl (which is not deprecated internally),
       // but we have to call Close, which is deprecated
       // from (1).
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
       Close();
#pragma GCC diagnostic pop
   }

That's the whole problem here from my point of view.

@YanzhaoW
Copy link
Author

Thanks for the replies.

Sorry I'm very busy working on other things and don't have time to deal with this PR these days. But probably on this Friday I will read the replies in details and continue to fix the things here if needed.

@YanzhaoW
Copy link
Author

Hi,

I thought it over again. Yes, the procedures described above is probably the right way to go. But there is still something weird that I couldn't pinpoint what exactly it is in the beginning, especially when I see:

private
   void CloseForInternalUse() {
       // All of these pragmas, because we would call
       // CloseImpl (which is not deprecated internally),
       // but we have to call Close, which is deprecated
       // from (1).
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
       Close();
#pragma GCC diagnostic pop
   }

For me, we are kind of contradicting ourself when we enable a warning from a new deprecation but at the same time we also disable it. I guess the people who designed the "deprecation" in C++ also didn't expect the people to disable it at the same time using a compiler-specific macro (There are some FairRoot users using apple-clang).

Our main dispute is whether we should remove the calling of the FairSink::Close() or keep it in the public API of FairRootManager:

void CloseSink()
{
    if (fSink) {
        fSink->Close();
    }
}

Here are my arguments why it should just be removed based on the following principles:

  1. Deprecation should only be applied to public API.
  2. Public API shouldn't be called internally.
  3. Virtual functions should be private and shouldn't be called in the derived classes.

The point 2 and 3 are supported by C++ guidelines from Herb Sutter about why virtual functions should be private. In this articles, it says

Prefer to use Template Method to make the interface stable and nonvirtual, while delegating customizable work to nonpublic virtual functions that are responsible for implementing the customizable behavior. After all, virtual functions are designed to let derived classes customize behavior; it's better to not let publicly derived classes also customize the inherited interface, which is supposed to be consistent.

It also implies here that the derived classes which overrides the virtual functions are just customization. For example, if we are designing an abstract class for a person wearing a T-shirt and pants. It's the job of the derived classes (User's classes) to decide what kind of T-shirt and pants are (e.g colors and sizes). But the logic connection between those objects are determined by the abstract class (the library implementer). Users shouldn't be concerned about in which order they are put up or whether they are put up or not. Suppose if we want to let the person wear T-shirt first instead of pants first, we could just change that order without doing any deprecation of the old order. Deprecation is used to notify the users. If it's irrelevant to users, it should not be deprecated but rather simply removed.

So now let's get back to CLoseSink(). Here the closing of the fSink is an internal logic for FairRootManager. Users of FairRootManager only interact with the public API CloseSink instead of fSink directly. For them, what's inside this public API shouldn't be their concerns and shouldn't break their code if changed. So if we need to discard the Close() method of FairSink, I think it's totally fine to be removed in FairRootManager::CloseSink() now.

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.

None yet

3 participants