Skip to content

Commit 4721ba6

Browse files
committed
global rebalance: preemption and stage notification
* reinforce monotonic rebalance generation admission - preemption now also waits for the old local rebalance instance to be cleared from `Reb` manager * make stage and abort notifications defensive against async cleanup (and DM nil) * send stage/abort notifications with the originating rebalance ID - instead of the manager's current ID * with minor refactoring and commenting Fixes #317 Signed-off-by: Alex Aizman <alex.aizman@gmail.com>
1 parent 6852ba6 commit 4721ba6

3 files changed

Lines changed: 90 additions & 34 deletions

File tree

reb/globrun.go

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const (
5050
rebStageFin
5151
rebStageFinStreams
5252
rebStageDone
53-
rebStageAbort // one of targets aborts the rebalancing (never set, only sent)
53+
rebStageAbort // one target aborts the rebalancing and notifies all (wire-only stage: never set, only sent)
5454
)
5555

5656
type (
@@ -140,7 +140,7 @@ const (
140140
preemptRetries = 16 // poll <= 16 seconds
141141
)
142142

143-
func (*Reb) _preempt(logHdr, oxid string) error {
143+
func (reb *Reb) _preempt(logHdr, oxid string) error {
144144
const (
145145
tag = "preempt"
146146
)
@@ -149,19 +149,35 @@ func (*Reb) _preempt(logHdr, oxid string) error {
149149
debug.AssertNoErr(err) // (can only be invalid xid)
150150
return err
151151
}
152-
if oxreb == nil || !oxreb.EndTime().IsZero() {
152+
if oxreb == nil {
153153
return nil
154154
}
155-
oxreb.Abort(cmn.ErrXactRenewAbort)
156-
nlog.Warningln(logHdr, "[", cmn.ErrXactRenewAbort, oxreb.String(), "]")
157155

158-
// reb.fini() always performs full cleanup, that's why we can just poll here
159-
// for valid end-time (not a sentinel indicating "almost done")
156+
if !oxreb.EndTime().IsZero() {
157+
xreb := reb.xctn()
158+
if xreb == nil || xreb.ID() != oxreb.ID() {
159+
return nil
160+
}
161+
}
162+
163+
s := oxreb.String()
164+
if oxreb.Abort(cmn.ErrXactRenewAbort) {
165+
nlog.Warningln(logHdr, "[", cmn.ErrXactRenewAbort, s, "]")
166+
} else {
167+
nlog.Warningln(logHdr, "[ previously already aborted or finished:", s, "]")
168+
}
169+
170+
// reb.fini() always performs full cleanup, that's why we can poll here until:
171+
// - xreb.Finish() publishes valid end-time
172+
// - reb.setXact(nil) uninstalls the old local rebalance
160173
smap1 := core.T.Sowner().Get()
161174
for i := range preemptRetries {
162175
time.Sleep(time.Second)
163176
if !oxreb.EndTime().IsZero() {
164-
return nil
177+
xreb := reb.xctn()
178+
if xreb == nil || xreb.ID() != oxreb.ID() {
179+
return nil
180+
}
165181
}
166182

167183
smap2 := core.T.Sowner().Get()
@@ -173,7 +189,8 @@ func (*Reb) _preempt(logHdr, oxid string) error {
173189
nlog.Warningf("%s: %s polling for %s ...", tag, logHdr, oxreb.String())
174190
}
175191
}
176-
return fmt.Errorf("%s: previous rebalance %q takes more than %v to finish", tag, oxreb.String(), preemptRetries*time.Second)
192+
193+
return fmt.Errorf("%s: previous rebalance %q takes more than %v to finish/cleanup", tag, oxreb.String(), preemptRetries*time.Second)
177194
}
178195

179196
// Run() is the main method: serialized to execute one at a time (while possibly _preempting_
@@ -196,10 +213,9 @@ func (*Reb) _preempt(logHdr, oxid string) error {
196213
//
197214
// See also: README.md in this package.
198215
func (reb *Reb) Run(smap *meta.Smap, extArgs *ExtArgs) {
199-
if reb.rebID() == extArgs.NID {
216+
if reb.skip(extArgs.NID, true /*allow equal*/) {
200217
return
201218
}
202-
203219
logHdr := reb.logHdr(extArgs.NID, smap, true /*initializing*/)
204220
// preempt
205221
if xact.IsValidRebID(extArgs.Oxid) {
@@ -234,7 +250,7 @@ func (reb *Reb) Run(smap *meta.Smap, extArgs *ExtArgs) {
234250
if !rargs.pingall() {
235251
return
236252
}
237-
if reb.rebID() == extArgs.NID {
253+
if reb.skip(extArgs.NID, true /*allow equal*/) {
238254
return
239255
}
240256

@@ -256,7 +272,13 @@ func (reb *Reb) Run(smap *meta.Smap, extArgs *ExtArgs) {
256272
reb.stages.stage.Store(rebStageDone)
257273
fs.RemoveMarker(fname.RebalanceMarker, extArgs.Tstats, false /*stopping*/)
258274
fs.RemoveMarker(fname.NodeRestartedPrev, extArgs.Tstats, false)
275+
reb.mu.Lock()
259276
rargs.xreb.Finish()
277+
if reb.xctn() == rargs.xreb {
278+
reb.setXact(nil) // (compare w/ fini())
279+
reb.smap.Store(nil)
280+
}
281+
reb.mu.Unlock()
260282
return
261283
}
262284

@@ -303,6 +325,7 @@ func (reb *Reb) Run(smap *meta.Smap, extArgs *ExtArgs) {
303325
} else {
304326
nlog.Errorln(logHdr, "fail => stage-fin:", err)
305327
}
328+
306329
reb.changeStage(rargs, rebStageFin)
307330

308331
reb.fini(rargs, err, extArgs.Tstats)
@@ -317,6 +340,21 @@ func (reb *Reb) Run(smap *meta.Smap, extArgs *ExtArgs) {
317340
}
318341
}
319342

343+
func (reb *Reb) skip(nid int64, allowEq bool) bool {
344+
// before _renew() commits successfully, reb.rebID() still reflects the previous generation
345+
cur := reb.rebID()
346+
switch {
347+
case cur < nid:
348+
return false
349+
case cur > nid:
350+
nlog.Warningf("skip rebalance g[%d] - already superseded by newer g[%d]", nid, cur)
351+
return true
352+
default:
353+
debug.Assert(allowEq) // see rns.IsRunning() check
354+
return true
355+
}
356+
}
357+
320358
// To optimize goroutine creation:
321359
// 1. One bucket case just calls a single rebalance worker depending on
322360
// whether a bucket is erasure coded (goroutine is not used).
@@ -353,14 +391,29 @@ func (reb *Reb) initRenew(rargs *rargs, extArgs *ExtArgs, ctlMsg func(sb *cos.SB
353391
if rns.Err != nil {
354392
return false
355393
}
356-
if rns.IsRunning() {
394+
if rns.IsRunning() { // this rargs.id is already running, nothing to do
357395
return false
358396
}
359397
xctn := rns.Entry.Get()
360398
xreb, ok := xctn.(*xs.Rebalance)
361399
debug.Assert(ok)
400+
if xreb.RebID() != rargs.id {
401+
err := fmt.Errorf("reb-id mismatch: g[%d] != g[%d]", xreb.RebID(), rargs.id)
402+
debug.AssertNoErr(err)
403+
return false
404+
}
362405

363406
reb.mu.Lock() // ----------------------------
407+
408+
if reb.skip(rargs.id, false /*allow equal*/) {
409+
reb.mu.Unlock()
410+
if xreb.RebID() < reb.rebID() {
411+
xreb.Abort(cmn.ErrXactRenewAbort)
412+
xreb.Finish()
413+
}
414+
return false
415+
}
416+
364417
origSmap, origStage := reb.smap.Load(), reb.stages.stage.Load()
365418
err := reb._renew(rargs, xreb, haveStreams)
366419

@@ -373,15 +426,9 @@ func (reb *Reb) initRenew(rargs *rargs, extArgs *ExtArgs, ctlMsg func(sb *cos.SB
373426
}
374427

375428
debug.Assert(reb.dm == nil)
376-
if reb.dm != nil { // (defensive; remove later)
377-
reb.dm.Close(err)
378-
reb.dm.UnregRecv()
379-
reb.dm = nil
380-
}
381-
if rargs.xreb == xreb {
382-
xreb.Abort(err)
383-
reb.setXact(nil)
384-
}
429+
debug.Assert(reb.xctn() != xreb)
430+
xreb.Abort(err)
431+
xreb.Finish()
385432
reb.smap.Store(origSmap)
386433
reb.stages.stage.Store(origStage)
387434
reb.mu.Unlock() // fail ------
@@ -391,10 +438,6 @@ func (reb *Reb) initRenew(rargs *rargs, extArgs *ExtArgs, ctlMsg func(sb *cos.SB
391438
}
392439

393440
func (reb *Reb) _renew(rargs *rargs, xreb *xs.Rebalance, haveStreams bool) error {
394-
if xreb.RebID() != rargs.id {
395-
return fmt.Errorf("reb-id mismatch: g[%d] != g[%d]", xreb.RebID(), rargs.id)
396-
}
397-
398441
rargs.xreb = xreb
399442

400443
// prior to opening streams:

reb/utils.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,29 @@ func (reb *Reb) warnID(remoteID int64, tid string) (s string) {
7878
return s
7979
}
8080

81-
// Rebalance moves to the next stage:
82-
// - update internal stage
83-
// - send notification to all other targets that this one is in a new stage
81+
// update internal stage and (best-effort) notify other targets:
82+
// - bcast (my rebID, my stage) with two possible "aborting" causes:
83+
// - a) local rebID < my rebID
84+
// - b) my stage == rebStageAbort
8485
func (reb *Reb) changeStage(rargs *rargs, newStage uint32) {
8586
rargs.stats.stage(newStage)
8687

8788
// set our own stage
8889
reb.stages.stage.Store(newStage)
8990

91+
dm := reb.dm
92+
if dm == nil {
93+
return
94+
}
95+
9096
// notify all
9197
var (
92-
ntfn = &stageNtfn{daemonID: core.T.SID(), stage: newStage, rebID: reb.rebID()}
98+
ntfn = &stageNtfn{daemonID: core.T.SID(), stage: newStage, rebID: rargs.id}
9399
hdr = transport.ObjHdr{}
94100
)
95101
hdr.Opaque = ntfn.NewPack(rebMsgNtfn)
96102

97-
if err := reb.dm.Notif(&hdr); err != nil {
103+
if err := dm.Notif(&hdr); err != nil {
98104
nlog.Warningln("failed to bcast new-stage notif: [", ntfn.rebID, stages[newStage], err, "]")
99105
}
100106
}
@@ -104,15 +110,19 @@ func (reb *Reb) abortAll(err error, xreb *xs.Rebalance) {
104110
if xreb == nil || !xreb.Abort(err) {
105111
return
106112
}
107-
nlog.InfoDepth(1, xreb.Name(), "abort-and-bcast", err)
113+
dm := reb.dm
114+
if dm == nil {
115+
return
116+
}
108117

118+
nlog.InfoDepth(1, xreb.Name(), "abort-and-bcast", err)
109119
var (
110-
ntfn = &stageNtfn{daemonID: core.T.SID(), rebID: reb.rebID(), stage: rebStageAbort}
120+
ntfn = &stageNtfn{daemonID: core.T.SID(), rebID: xreb.RebID(), stage: rebStageAbort}
111121
hdr = transport.ObjHdr{}
112122
)
113123
hdr.Opaque = ntfn.NewPack(rebMsgNtfn)
114124

115-
if err := reb.dm.Notif(&hdr); err != nil {
125+
if err := dm.Notif(&hdr); err != nil {
116126
nlog.Errorln("failed to bcast abort notif: [", ntfn.rebID, err, "]")
117127
}
118128
}

xact/base.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ func (xctn *Base) Abort(err error) bool {
187187
}
188188

189189
func (xctn *Base) Finish() {
190+
// - CAS 0 -> finishSentinel: running => finishing
191+
// - CAS MSB int64 -> finishSentinel: stopping => finishing
192+
// - otherwise, nothing to do
190193
if !xctn.eutime.CAS(0, finishSentinel) && !xctn.eutime.CAS(cos.MSB64, finishSentinel) {
191194
return
192195
}

0 commit comments

Comments
 (0)