1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "librbd/operation/TrimRequest.h"
5 #include "librbd/AsyncObjectThrottle.h"
6 #include "librbd/ExclusiveLock.h"
7 #include "librbd/ImageCtx.h"
8 #include "librbd/internal.h"
9 #include "librbd/ObjectMap.h"
10 #include "librbd/Utils.h"
11 #include "librbd/io/ObjectRequest.h"
12 #include "common/ContextCompletion.h"
13 #include "common/dout.h"
14 #include "common/errno.h"
15 #include "osdc/Striper.h"
17 #include <boost/bind.hpp>
18 #include <boost/lambda/bind.hpp>
19 #include <boost/lambda/construct.hpp>
20 #include <boost/scope_exit.hpp>
22 #define dout_subsys ceph_subsys_rbd
24 #define dout_prefix *_dout << "librbd::TrimRequest: "
30 class C_CopyupObject
: public C_AsyncObjectThrottle
<I
> {
32 C_CopyupObject(AsyncObjectThrottle
<I
> &throttle
, I
*image_ctx
,
33 ::SnapContext snapc
, uint64_t object_no
)
34 : C_AsyncObjectThrottle
<I
>(throttle
, *image_ctx
), m_snapc(snapc
),
35 m_object_no(object_no
)
40 I
&image_ctx
= this->m_image_ctx
;
41 assert(image_ctx
.owner_lock
.is_locked());
42 assert(image_ctx
.exclusive_lock
== nullptr ||
43 image_ctx
.exclusive_lock
->is_lock_owner());
45 string oid
= image_ctx
.get_object_name(m_object_no
);
46 ldout(image_ctx
.cct
, 10) << "removing (with copyup) " << oid
<< dendl
;
48 auto req
= io::ObjectRequest
<I
>::create_discard(
49 &image_ctx
, oid
, m_object_no
, 0, image_ctx
.layout
.object_size
, m_snapc
,
50 false, false, {}, this);
55 ::SnapContext m_snapc
;
60 class C_RemoveObject
: public C_AsyncObjectThrottle
<I
> {
62 C_RemoveObject(AsyncObjectThrottle
<I
> &throttle
, I
*image_ctx
,
64 : C_AsyncObjectThrottle
<I
>(throttle
, *image_ctx
), m_object_no(object_no
)
69 I
&image_ctx
= this->m_image_ctx
;
70 assert(image_ctx
.owner_lock
.is_locked());
71 assert(image_ctx
.exclusive_lock
== nullptr ||
72 image_ctx
.exclusive_lock
->is_lock_owner());
75 RWLock::RLocker
snap_locker(image_ctx
.snap_lock
);
76 if (image_ctx
.object_map
!= nullptr &&
77 !image_ctx
.object_map
->object_may_exist(m_object_no
)) {
82 string oid
= image_ctx
.get_object_name(m_object_no
);
83 ldout(image_ctx
.cct
, 10) << "removing " << oid
<< dendl
;
85 librados::AioCompletion
*rados_completion
=
86 util::create_rados_callback(this);
87 int r
= image_ctx
.data_ctx
.aio_remove(oid
, rados_completion
);
89 rados_completion
->release();
98 TrimRequest
<I
>::TrimRequest(I
&image_ctx
, Context
*on_finish
,
99 uint64_t original_size
, uint64_t new_size
,
100 ProgressContext
&prog_ctx
)
101 : AsyncRequest
<I
>(image_ctx
, on_finish
), m_new_size(new_size
),
104 uint64_t period
= image_ctx
.get_stripe_period();
105 uint64_t new_num_periods
= ((m_new_size
+ period
- 1) / period
);
106 m_delete_off
= MIN(new_num_periods
* period
, original_size
);
107 // first object we can delete free and clear
108 m_delete_start
= new_num_periods
* image_ctx
.get_stripe_count();
109 m_delete_start_min
= m_delete_start
;
110 m_num_objects
= Striper::get_num_objects(image_ctx
.layout
, original_size
);
112 CephContext
*cct
= image_ctx
.cct
;
113 ldout(cct
, 10) << this << " trim image " << original_size
<< " -> "
114 << m_new_size
<< " periods " << new_num_periods
115 << " discard to offset " << m_delete_off
116 << " delete objects " << m_delete_start
117 << " to " << m_num_objects
<< dendl
;
120 template <typename I
>
121 bool TrimRequest
<I
>::should_complete(int r
)
123 I
&image_ctx
= this->m_image_ctx
;
124 CephContext
*cct
= image_ctx
.cct
;
125 ldout(cct
, 5) << this << " should_complete: r=" << r
<< dendl
;
126 if (r
== -ERESTART
) {
127 ldout(cct
, 5) << "trim operation interrupted" << dendl
;
130 lderr(cct
) << "trim encountered an error: " << cpp_strerror(r
) << dendl
;
134 RWLock::RLocker
owner_lock(image_ctx
.owner_lock
);
137 ldout(cct
, 5) << " PRE_TRIM" << dendl
;
138 send_copyup_objects();
141 case STATE_COPYUP_OBJECTS
:
142 ldout(cct
, 5) << " COPYUP_OBJECTS" << dendl
;
143 send_remove_objects();
146 case STATE_REMOVE_OBJECTS
:
147 ldout(cct
, 5) << " REMOVE_OBJECTS" << dendl
;
151 case STATE_POST_TRIM
:
152 ldout(cct
, 5) << " POST_TRIM" << dendl
;
153 send_clean_boundary();
156 case STATE_CLEAN_BOUNDARY
:
157 ldout(cct
, 5) << "CLEAN_BOUNDARY" << dendl
;
162 ldout(cct
, 5) << "FINISHED" << dendl
;
166 lderr(cct
) << "invalid state: " << m_state
<< dendl
;
173 template <typename I
>
174 void TrimRequest
<I
>::send() {
179 void TrimRequest
<I
>::send_pre_trim() {
180 I
&image_ctx
= this->m_image_ctx
;
181 assert(image_ctx
.owner_lock
.is_locked());
183 if (m_delete_start
>= m_num_objects
) {
184 send_clean_boundary();
189 RWLock::RLocker
snap_locker(image_ctx
.snap_lock
);
190 if (image_ctx
.object_map
!= nullptr) {
191 ldout(image_ctx
.cct
, 5) << this << " send_pre_trim: "
192 << " delete_start_min=" << m_delete_start_min
193 << " num_objects=" << m_num_objects
<< dendl
;
194 m_state
= STATE_PRE_TRIM
;
196 assert(image_ctx
.exclusive_lock
->is_lock_owner());
198 RWLock::WLocker
object_map_locker(image_ctx
.object_map_lock
);
199 if (image_ctx
.object_map
->template aio_update
<AsyncRequest
<I
> >(
200 CEPH_NOSNAP
, m_delete_start_min
, m_num_objects
, OBJECT_PENDING
,
201 OBJECT_EXISTS
, {}, this)) {
207 send_copyup_objects();
211 void TrimRequest
<I
>::send_copyup_objects() {
212 I
&image_ctx
= this->m_image_ctx
;
213 assert(image_ctx
.owner_lock
.is_locked());
217 uint64_t parent_overlap
;
219 RWLock::RLocker
snap_locker(image_ctx
.snap_lock
);
220 RWLock::RLocker
parent_locker(image_ctx
.parent_lock
);
222 snapc
= image_ctx
.snapc
;
223 has_snapshots
= !image_ctx
.snaps
.empty();
224 int r
= image_ctx
.get_parent_overlap(CEPH_NOSNAP
, &parent_overlap
);
228 // copyup is only required for portion of image that overlaps parent
229 uint64_t copyup_end
= Striper::get_num_objects(image_ctx
.layout
,
232 // TODO: protect against concurrent shrink and snap create?
233 // skip to remove if no copyup is required.
234 if (copyup_end
<= m_delete_start
|| !has_snapshots
) {
235 send_remove_objects();
239 uint64_t copyup_start
= m_delete_start
;
240 m_delete_start
= copyup_end
;
242 ldout(image_ctx
.cct
, 5) << this << " send_copyup_objects: "
243 << " start object=" << copyup_start
<< ", "
244 << " end object=" << copyup_end
<< dendl
;
245 m_state
= STATE_COPYUP_OBJECTS
;
247 Context
*ctx
= this->create_callback_context();
248 typename AsyncObjectThrottle
<I
>::ContextFactory
context_factory(
249 boost::lambda::bind(boost::lambda::new_ptr
<C_CopyupObject
<I
> >(),
250 boost::lambda::_1
, &image_ctx
, snapc
, boost::lambda::_2
));
251 AsyncObjectThrottle
<I
> *throttle
= new AsyncObjectThrottle
<I
>(
252 this, image_ctx
, context_factory
, ctx
, &m_prog_ctx
, copyup_start
,
254 throttle
->start_ops(image_ctx
.concurrent_management_ops
);
257 template <typename I
>
258 void TrimRequest
<I
>::send_remove_objects() {
259 I
&image_ctx
= this->m_image_ctx
;
260 assert(image_ctx
.owner_lock
.is_locked());
262 ldout(image_ctx
.cct
, 5) << this << " send_remove_objects: "
263 << " delete_start=" << m_delete_start
264 << " num_objects=" << m_num_objects
<< dendl
;
265 m_state
= STATE_REMOVE_OBJECTS
;
267 Context
*ctx
= this->create_callback_context();
268 typename AsyncObjectThrottle
<I
>::ContextFactory
context_factory(
269 boost::lambda::bind(boost::lambda::new_ptr
<C_RemoveObject
<I
> >(),
270 boost::lambda::_1
, &image_ctx
, boost::lambda::_2
));
271 AsyncObjectThrottle
<I
> *throttle
= new AsyncObjectThrottle
<I
>(
272 this, image_ctx
, context_factory
, ctx
, &m_prog_ctx
, m_delete_start
,
274 throttle
->start_ops(image_ctx
.concurrent_management_ops
);
278 void TrimRequest
<I
>::send_post_trim() {
279 I
&image_ctx
= this->m_image_ctx
;
280 assert(image_ctx
.owner_lock
.is_locked());
283 RWLock::RLocker
snap_locker(image_ctx
.snap_lock
);
284 if (image_ctx
.object_map
!= nullptr) {
285 ldout(image_ctx
.cct
, 5) << this << " send_post_trim:"
286 << " delete_start_min=" << m_delete_start_min
287 << " num_objects=" << m_num_objects
<< dendl
;
288 m_state
= STATE_POST_TRIM
;
290 assert(image_ctx
.exclusive_lock
->is_lock_owner());
292 RWLock::WLocker
object_map_locker(image_ctx
.object_map_lock
);
293 if (image_ctx
.object_map
->template aio_update
<AsyncRequest
<I
> >(
294 CEPH_NOSNAP
, m_delete_start_min
, m_num_objects
, OBJECT_NONEXISTENT
,
295 OBJECT_PENDING
, {}, this)) {
301 send_clean_boundary();
304 template <typename I
>
305 void TrimRequest
<I
>::send_clean_boundary() {
306 I
&image_ctx
= this->m_image_ctx
;
307 assert(image_ctx
.owner_lock
.is_locked());
308 CephContext
*cct
= image_ctx
.cct
;
309 if (m_delete_off
<= m_new_size
) {
314 // should have been canceled prior to releasing lock
315 assert(image_ctx
.exclusive_lock
== nullptr ||
316 image_ctx
.exclusive_lock
->is_lock_owner());
317 uint64_t delete_len
= m_delete_off
- m_new_size
;
318 ldout(image_ctx
.cct
, 5) << this << " send_clean_boundary: "
319 << " delete_off=" << m_delete_off
320 << " length=" << delete_len
<< dendl
;
321 m_state
= STATE_CLEAN_BOUNDARY
;
325 RWLock::RLocker
snap_locker(image_ctx
.snap_lock
);
326 snapc
= image_ctx
.snapc
;
329 // discard the weird boundary
330 std::vector
<ObjectExtent
> extents
;
331 Striper::file_to_extents(cct
, image_ctx
.format_string
,
332 &image_ctx
.layout
, m_new_size
, delete_len
, 0,
335 ContextCompletion
*completion
=
336 new ContextCompletion(this->create_async_callback_context(), true);
337 for (vector
<ObjectExtent
>::iterator p
= extents
.begin();
338 p
!= extents
.end(); ++p
) {
339 ldout(cct
, 20) << " ex " << *p
<< dendl
;
340 Context
*req_comp
= new C_ContextCompletion(*completion
);
342 if (p
->offset
== 0) {
343 // treat as a full object delete on the boundary
344 p
->length
= image_ctx
.layout
.object_size
;
346 auto req
= io::ObjectRequest
<I
>::create_discard(&image_ctx
, p
->oid
.name
,
347 p
->objectno
, p
->offset
,
348 p
->length
, snapc
, false,
352 completion
->finish_adding_requests();
355 template <typename I
>
356 void TrimRequest
<I
>::send_finish(int r
) {
357 m_state
= STATE_FINISHED
;
358 this->async_complete(r
);
361 } // namespace operation
362 } // namespace librbd
364 template class librbd::operation::TrimRequest
<librbd::ImageCtx
>;