2653. [filesys.ts] [PDTS] directory_entry multithreading concerns

Section: 12 [filesys.ts::class.directory_entry] Status: TS Submitter: Stephan T. Lavavej Opened: 2014-02-03 Last modified: 2017-07-30

Priority: Not Prioritized

View all other issues in [filesys.ts::class.directory_entry].

View all issues with TS status.

Discussion:

Addresses: filesys.ts

12 [class.directory_entry] depicts directory_entry as having mutable m_status and m_symlink_status data members. This is problematic because of C++11's multithreading guarantees, which say that const member functions are simultaneously callable. As a result, mutable data members must be protected with synchronization, which was probably not considered during this design. The complexity and expense may be acceptable in directory_entry (since it can save filesystem queries, apparently) but it would be very very nice to have a note about this.

[2014-02-13 LWG/SG-3 discussed in Issaquah: Beman and STL will work together to better understand the concerns, and to formulate a solution. This is not a blocking issue for finishing the TS, but is a blocking issue for merging into the IS]

[2014-04-17 Beman provides proposed wording]

[2014-06-19 Rapperswil LWG decides to remove mutable private members. Beman provides wording.]

Proposed resolution:

Change 12 [class.directory_entry] as indicated:

    namespace std { namespace experimental { namespace filesystem { inline namespace v1 {

    class directory_entry
    {
    public:

    // constructors and destructor
    directory_entry() = default;
    directory_entry(const directory_entry&) = default;
    directory_entry(directory_entry&&) noexcept = default;
    explicit directory_entry(const path& p);
    explicit directory_entry(const path& p, file_status st=file_status(),
      file_status symlink_st=file_status());
    ~directory_entry();

    // modifiers
    directory_entry& operator=(const directory_entry&) = default;
    directory_entry& operator=(directory_entry&&) noexcept = default;
    void assign(const path& p);
    void assign(const path& p, file_status st=file_status(),
      file_status symlink_st=file_status());
    void replace_filename(const path& p);
    void replace_filename(const path& p, file_status st=file_status(),
      file_status symlink_st=file_status());

    // observers
    const path&  path() const noexcept;
    file_status  status() const;
    file_status  status(error_code& ec) const noexcept;
    file_status  symlink_status() const;
    file_status  symlink_status(error_code& ec) const noexcept;

    bool operator< (const directory_entry& rhs) const noexcept;
    bool operator==(const directory_entry& rhs) const noexcept;
    bool operator!=(const directory_entry& rhs) const noexcept;
    bool operator<=(const directory_entry& rhs) const noexcept;
    bool operator> (const directory_entry& rhs) const noexcept;
    bool operator>=(const directory_entry& rhs) const noexcept;
    private:
    path                 m_path;           // for exposition only
    mutable file_status  m_status;         // for exposition only; stat()-like
    mutable file_status  m_symlink_status; // for exposition only; lstat()-like
    };

    } } } }  // namespaces std::experimental::filesystem::v1
  

A directory_entry object stores a path object, a file_status object for non-symbolic link status, and a file_status object for symbolic link status. The file_status objects act as value caches.

[Note: Because status()on a pathname may be a relatively expensive operation, some operating systems provide status information as a byproduct of directory iteration. Caching such status information can result in significant time savings. Cached and non-cached results may differ in the presence of file system races. —end note]

12.1 directory_entry constructors [directory_entry.cons]

Add the description of this constructor in its entirety:

explicit directory_entry(const path& p);

Effects: Constructs an object of type directory_entry

Postcondition: path() == p.

explicit directory_entry(const path& p, file_status st=file_status(),
    file_status symlink_st=file_status());

Strike the description

12.2 directory_entry modifiers [directory_entry.mods]

Add the description of this function in its entirety:

void assign(const path& p);

Postcondition: path() == p.

void assign(const path& p, file_status st=file_status(),
  file_status symlink_st=file_status());

Strike the description

Add the description of this function in its entirety:

void replace_filename(const path& p);

Postcondition: path() == x.parent_path() / p where x is the value of path() before the function is called.

void replace_filename(const path& p, file_status st=file_status(),
  file_status symlink_st=file_status());

Strike the description

12.3 directory_entry observers [directory_entry.obs]

const path& path() const noexcept;

Returns: m_path

file_status status() const;
file_status status(error_code& ec) const noexcept;

Effects: As if,

if (!status_known(m_status))
{
  if (status_known(m_symlink_status) && !is_symlink(m_symlink_status))
    { m_status = m_symlink_status; }
  else { m_status = status(m_path[, ec]); }
}

Returns: m_status status(path()[, ec]) .

Throws: As specified in Error reporting (7).

file_status  symlink_status() const;
file_status  symlink_status(error_code& ec) const noexcept;

Effects: As if,


        if (!status_known(m_symlink_status))
        {
        m_symlink_status = symlink_status(m_path[, ec]);
        }

Returns: m_symlink_status symlink_status(path()[, ec]) .

Throws: As specified in Error reporting (7).

bool operator==(const directory_entry& rhs) const noexcept;

Returns: m_path == rhs.m_path.

[Note: Status members do not participate in determining equality. — end note]

bool operator!=(const directory_entry& rhs) const noexcept;

Returns: m_path != rhs.m_path.

bool operator< (const directory_entry& rhs) const noexcept;

Returns: m_path < rhs.m_path.

bool operator<=(const directory_entry& rhs) const noexcept;

Returns: m_path <= rhs.m_path.

bool operator> (const directory_entry& rhs) const noexcept;

Returns: m_path > rhs.m_path.

bool operator>=(const directory_entry& rhs) const noexcept;

Returns: m_path >= rhs.m_path.