Skip to content

Commit ee9b357

Browse files
authored
Merge pull request #1516 from johnhaddon/cancellerImprovements
Canceller improvements
2 parents 8e7880b + 519cc90 commit ee9b357

5 files changed

Lines changed: 345 additions & 57 deletions

File tree

Changes

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
10.7.x.x (relative to 10.7.0.0a4)
22
========
33

4-
API
5-
---
4+
Improvements
5+
------------
66

7+
- Canceller :
8+
- Derived from RefCounted, to allow Cancellers to be shared between multiple clients.
9+
- Enabled chaining of Cancellers, via `addChild()` and `removeChild()` methods and a `ScopedChild` utility class.
710
- MessageHandler : Added `msg()` overload that accepts a `fmt::format_string` and arguments.
811
- InternedString : Added specialisation for `fmt::formatter<InternedString>`.
912

13+
Breaking Changes
14+
----------------
15+
16+
- Canceller : Changed base class.
17+
1018
Build
1119
-----
1220

include/IECore/Canceller.h

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,11 @@
3535
#ifndef IECORE_CANCELLER_H
3636
#define IECORE_CANCELLER_H
3737

38-
#include "IECore/Export.h"
39-
40-
#include "boost/noncopyable.hpp"
38+
#include "IECore/RefCounted.h"
4139

4240
#include <atomic>
4341
#include <chrono>
42+
#include <mutex>
4443

4544
namespace IECore
4645
{
@@ -61,77 +60,81 @@ struct IECORE_API Cancelled
6160
/// Example :
6261
///
6362
/// ```
64-
/// Canceller c;
63+
/// CancellerPtr c = new Canceller;
6564
/// thread t(
6665
/// [&c] {
6766
/// while( 1 ) {
68-
/// Canceller::check( &c );
67+
/// Canceller::check( c.get() );
6968
/// }
7069
/// }
7170
/// );
7271
/// c.cancel();
7372
/// t.join();
7473
/// ```
75-
class Canceller : public boost::noncopyable
74+
class IECORE_API Canceller : public IECore::RefCounted
7675
{
7776

7877
public :
7978

80-
Canceller()
81-
: m_cancelled( false ), m_cancellationTime( 0 )
82-
{
83-
}
79+
Canceller();
8480

85-
void cancel()
86-
{
87-
// Store time of first cancellation. We use `compare_exchange_weak()`
88-
// to avoid unwanted updates on subsequent calls.
89-
std::chrono::steady_clock::rep epoch( 0 );
90-
m_cancellationTime.compare_exchange_weak(
91-
epoch,
92-
std::chrono::steady_clock::now().time_since_epoch().count()
93-
);
94-
// Set cancellation flag _after_ storing time, so that
95-
// `elapsedTime()` always sees a valid time.
96-
m_cancelled = true;
97-
}
98-
99-
bool cancelled() const
100-
{
101-
/// \todo Can we reduce overhead by reading
102-
/// with a more relaxed memory ordering here?
103-
return m_cancelled;
104-
}
81+
IE_CORE_DECLAREMEMBERPTR( Canceller )
10582

106-
static void check( const Canceller *canceller )
107-
{
108-
if( canceller && canceller->cancelled() )
109-
{
110-
throw Cancelled();
111-
}
112-
}
83+
void cancel();
84+
bool cancelled() const;
85+
86+
/// Throws `IECore::Cancelled` if `canceller` is non-null and has
87+
/// been cancelled, otherwise does nothing.
88+
static void check( const Canceller *canceller );
11389

11490
/// Returns the time passed since `cancel()` was first called, or `0` if
11591
/// it has not been called yet.
116-
std::chrono::steady_clock::duration elapsedTime() const
92+
std::chrono::steady_clock::duration elapsedTime() const;
93+
94+
/// Adds a child canceller that will be cancelled automatically
95+
/// when this is cancelled. If this is already cancels, then the
96+
/// child is cancelled immediately.
97+
void addChild( const Ptr &child );
98+
99+
/// Removes a child canceller. Additions are counted, and actual
100+
/// removal only occurs when the number of removals equals the number
101+
/// of additions.
102+
void removeChild( const Ptr &child );
103+
104+
/// Convenience class to manage removal of children in an
105+
/// exception-safe way.
106+
class IECORE_API ScopedChild : boost::noncopyable
117107
{
118-
if( m_cancelled )
119-
{
120-
return std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point( std::chrono::steady_clock::duration( m_cancellationTime ) );
121-
}
122-
else
123-
{
124-
return std::chrono::steady_clock::duration( 0 );
125-
}
126-
}
108+
109+
public :
110+
111+
/// Adds `child` to `parent`.
112+
ScopedChild( Canceller *parent, const Ptr &child );
113+
/// Removes `child` from `parent`.
114+
~ScopedChild();
115+
116+
private :
117+
118+
Canceller *m_parent;
119+
Ptr m_child;
120+
121+
};
122+
127123

128124
private :
129125

130126
std::atomic_bool m_cancelled;
131127
std::atomic<std::chrono::steady_clock::time_point::rep> m_cancellationTime;
132128

129+
std::mutex m_childrenMutex;
130+
std::unordered_map<Ptr, size_t> m_children;
131+
133132
};
134133

134+
IE_CORE_DECLAREPTR( Canceller )
135+
135136
}; // namespace IECore
136137

138+
#include "IECore/Canceller.inl"
139+
137140
#endif // IECORE_CANCELLER_H

include/IECore/Canceller.inl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright (c) 2018, Image Engine Design Inc. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are
7+
// met:
8+
//
9+
// * Redistributions of source code must retain the above copyright
10+
// notice, this list of conditions and the following disclaimer.
11+
//
12+
// * Redistributions in binary form must reproduce the above copyright
13+
// notice, this list of conditions and the following disclaimer in the
14+
// documentation and/or other materials provided with the distribution.
15+
//
16+
// * Neither the name of Image Engine Design nor the names of any
17+
// other contributors to this software may be used to endorse or
18+
// promote products derived from this software without specific prior
19+
// written permission.
20+
//
21+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22+
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23+
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24+
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
25+
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26+
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27+
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28+
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29+
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30+
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31+
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
//
33+
//////////////////////////////////////////////////////////////////////////
34+
35+
#ifndef IECORE_CANCELLER_INL
36+
#define IECORE_CANCELLER_INL
37+
38+
namespace IECore
39+
{
40+
41+
inline Canceller::Canceller()
42+
: m_cancelled( false ), m_cancellationTime( 0 )
43+
{
44+
}
45+
46+
inline void Canceller::cancel()
47+
{
48+
// Store time of first cancellation. We use `compare_exchange_weak()`
49+
// to avoid unwanted updates on subsequent calls.
50+
std::chrono::steady_clock::rep epoch( 0 );
51+
m_cancellationTime.compare_exchange_weak(
52+
epoch,
53+
std::chrono::steady_clock::now().time_since_epoch().count()
54+
);
55+
// Set cancellation flag _after_ storing time, so that
56+
// `elapsedTime()` always sees a valid time.
57+
if( !m_cancelled.exchange( true ) )
58+
{
59+
std::lock_guard lock( m_childrenMutex );
60+
for( const auto &[child, count] : m_children )
61+
{
62+
child->cancel();
63+
}
64+
};
65+
}
66+
67+
inline bool Canceller::cancelled() const
68+
{
69+
/// \todo Can we reduce overhead by reading
70+
/// with a more relaxed memory ordering here?
71+
return m_cancelled;
72+
}
73+
74+
inline void Canceller::check( const Canceller *canceller )
75+
{
76+
if( canceller && canceller->cancelled() )
77+
{
78+
throw Cancelled();
79+
}
80+
}
81+
82+
inline std::chrono::steady_clock::duration Canceller::elapsedTime() const
83+
{
84+
if( m_cancelled )
85+
{
86+
return std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point( std::chrono::steady_clock::duration( m_cancellationTime ) );
87+
}
88+
else
89+
{
90+
return std::chrono::steady_clock::duration( 0 );
91+
}
92+
}
93+
94+
inline void Canceller::addChild( const Ptr &child )
95+
{
96+
std::lock_guard lock( m_childrenMutex );
97+
m_children[child]++;
98+
if( m_cancelled )
99+
{
100+
child->cancel();
101+
}
102+
}
103+
104+
inline void Canceller::removeChild( const Ptr &child )
105+
{
106+
std::lock_guard lock( m_childrenMutex );
107+
auto it = m_children.find( child );
108+
if( it != m_children.end() )
109+
{
110+
if( --it->second == 0 )
111+
{
112+
m_children.erase( it );
113+
}
114+
}
115+
}
116+
117+
inline Canceller::ScopedChild::ScopedChild( Canceller *parent, const Ptr &child )
118+
: m_parent( parent ), m_child( child )
119+
{
120+
m_parent->addChild( m_child );
121+
}
122+
123+
inline Canceller::ScopedChild::~ScopedChild()
124+
{
125+
m_parent->removeChild( m_child );
126+
}
127+
128+
}; // namespace IECore
129+
130+
#endif // IECORE_CANCELLER_INL

src/IECorePython/CancellerBinding.cpp

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@
3838

3939
#include "IECorePython/CancellerBinding.h"
4040
#include "IECorePython/ExceptionBinding.h"
41+
#include "IECorePython/RefCountedBinding.h"
4142

4243
#include "IECore/Canceller.h"
4344

45+
#include <optional>
46+
4447
using namespace boost::python;
4548
using namespace IECore;
4649

@@ -52,6 +55,35 @@ double elapsedTimeWrapper( const Canceller &canceller )
5255
return std::chrono::duration<double>( canceller.elapsedTime() ).count();
5356
}
5457

58+
class ScopedChildWrapper : public boost::noncopyable
59+
{
60+
61+
public :
62+
63+
ScopedChildWrapper( Canceller &parent, Canceller &child )
64+
: m_parent( &parent ), m_child( &child )
65+
{
66+
}
67+
68+
void enter()
69+
{
70+
m_scope.emplace( m_parent.get(), m_child );
71+
}
72+
73+
bool exit( boost::python::object excType, boost::python::object excValue, boost::python::object excTraceBack )
74+
{
75+
m_scope.reset();
76+
return false; // Don't suppress exceptions
77+
}
78+
79+
private :
80+
81+
CancellerPtr m_parent;
82+
CancellerPtr m_child;
83+
std::optional<Canceller::ScopedChild> m_scope;
84+
85+
};
86+
5587
} // namespace
5688

5789
namespace IECorePython
@@ -60,15 +92,24 @@ namespace IECorePython
6092
void bindCanceller()
6193
{
6294

63-
class_<Canceller, boost::noncopyable>( "Canceller" )
64-
.def( "cancel", &Canceller::cancel )
65-
.def( "cancelled", &Canceller::cancelled )
66-
.def( "check", &Canceller::check )
67-
.staticmethod( "check" )
68-
.def( "elapsedTime", &elapsedTimeWrapper )
69-
;
95+
{
96+
scope s = RefCountedClass<Canceller, RefCounted>( "Canceller" )
97+
.def( init<>() )
98+
.def( "cancel", &Canceller::cancel )
99+
.def( "cancelled", &Canceller::cancelled )
100+
.def( "check", &Canceller::check )
101+
.staticmethod( "check" )
102+
.def( "elapsedTime", &elapsedTimeWrapper )
103+
.def( "addChild", &Canceller::addChild )
104+
.def( "removeChild", &Canceller::removeChild )
105+
;
70106

71-
register_ptr_to_python<std::shared_ptr<Canceller>>();
107+
class_<ScopedChildWrapper, boost::noncopyable>( "ScopedChild", no_init )
108+
.def( init<Canceller &, Canceller &>() )
109+
.def( "__enter__", &ScopedChildWrapper::enter, return_self<>() )
110+
.def( "__exit__", &ScopedChildWrapper::exit )
111+
;
112+
}
72113

73114
ExceptionClass<Cancelled>( "Cancelled", PyExc_RuntimeError )
74115
.def( init<>() )

0 commit comments

Comments
 (0)