Skip to content

Commit 532f71e

Browse files
committed
nfs41: refactor and fix read delegation handling
Motivation: current delegation code has following issues: - open squashing happens before recall of conflicting delegation - client can conflict with himself - invalidate delegations on close - uses Stream#peak is an anti-pattern to recall delegations - delegation stateids not disposed on return Modification: - Move delegation conflict check ahead of open processing - ignore conflicts from the same client - rename canDelegate to canDelegateRead (just in case we are write delegations too) - don't dispose delegation on close (but on delegation return) - replace peak+count with reduce Result: better spec compliance, clean code. Acked-by: Marina Sahakyan Target: master
1 parent a5777a3 commit 532f71e

File tree

2 files changed

+57
-65
lines changed

2 files changed

+57
-65
lines changed

core/src/main/java/org/dcache/nfs/v4/FileTracker.java

Lines changed: 50 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,16 @@
4242
import org.dcache.nfs.v4.xdr.stateid4;
4343
import org.dcache.nfs.vfs.Inode;
4444
import org.dcache.nfs.util.Opaque;
45+
import org.slf4j.Logger;
46+
import org.slf4j.LoggerFactory;
4547

4648
/**
4749
* A class which tracks open files.
4850
*/
4951
public class FileTracker {
5052

53+
public static final Logger LOG = LoggerFactory.getLogger(FileTracker.class);
54+
5155
/*
5256
* we use {@link Striped} locks here to split synchronized block on open files
5357
* into multiple partitions to increase concurrency, while guaranteeing atomicity
@@ -135,7 +139,7 @@ public NFS4Client getClient() {
135139
* @param stateid
136140
* @param delegationType
137141
*/
138-
record DelegationState(NFS4Client client, stateid4 openStateId, stateid4 stateid, int delegationType) {
142+
record DelegationState(NFS4Client client, NFS4State delegationStateid, int delegationType) {
139143

140144
}
141145

@@ -187,6 +191,43 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int
187191
throw new ShareDeniedException("Conflicting share");
188192
}
189193

194+
/*
195+
* REVISIT: currently only read-delegations are supported
196+
*/
197+
var existingDelegations = delegations.get(fileId);
198+
199+
/*
200+
* delegation is possible if:
201+
* - client has a callback channel
202+
* - client does not have a delegation for this file
203+
* - no other open has write access
204+
*/
205+
boolean canDelegateRead = client.getCB() != null &&
206+
(existingDelegations == null || existingDelegations.stream().noneMatch(d -> d.client().getId() == client.getId())) &&
207+
opens.stream().noneMatch(os -> (os.shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WRITE) != 0);
208+
209+
// recall any read delegations if write
210+
if ((existingDelegations != null) && (shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WRITE) != 0) {
211+
var fh = new nfs_fh4(inode.toNfsHandle());
212+
int recalledDelegations = existingDelegations.stream()
213+
.filter(d -> d.client().isLeaseValid())
214+
.filter(d -> !d.client().getId().equals(client.getId()))
215+
.reduce(0, (c, d) -> {
216+
try {
217+
d.client().getCB().cbDelegationRecall(fh, d.delegationStateid().stateid(), false);
218+
return c + 1;
219+
} catch (IOException e) {
220+
LOG.warn("Failed to recall delegation from {} : {}", d.client(), e.toString());
221+
d.delegationStateid().disposeIgnoreFailures();
222+
return c;
223+
}
224+
}, Integer::sum);
225+
226+
if (recalledDelegations > 0) {
227+
throw new DelayException("Recalling read delegations");
228+
}
229+
}
230+
190231
// if there is another open from the same client we must merge
191232
// access mode and return the same stateid as required by rfc5661#18.16.3
192233

@@ -210,41 +251,6 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int
210251
}
211252
}
212253

213-
/*
214-
* REVISIT: currently only read-delegations are supported
215-
*/
216-
var existingDelegations = delegations.get(fileId);
217-
218-
/*
219-
* delegation is possible if:
220-
* - client has a callback channel
221-
* - client does not have a delegation for this file
222-
* - no other open has write access
223-
*/
224-
boolean canDelegate = client.getCB() != null &&
225-
(existingDelegations == null || existingDelegations.stream().noneMatch(d -> d.client().getId() == client.getId())) &&
226-
opens.stream().noneMatch(os -> (os.shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WRITE) != 0);
227-
228-
// recall any read delegations if write
229-
if ((existingDelegations != null) && (shareAccess & nfs4_prot.OPEN4_SHARE_ACCESS_WRITE) != 0) {
230-
231-
// REVISIT: usage of Stream#peek is an anti-pattern
232-
boolean haveRecalled = existingDelegations.stream()
233-
.filter(d -> client.isLeaseValid())
234-
.peek(d -> {
235-
try {
236-
d.client().getCB()
237-
.cbDelegationRecall(new nfs_fh4(inode.toNfsHandle()), d.stateid(), false);
238-
} catch (IOException e) {
239-
// ignore
240-
}
241-
}).count() > 0;
242-
243-
if (haveRecalled) {
244-
throw new DelayException("Recalling read delegations");
245-
}
246-
}
247-
248254
NFS4State state = client.createState(owner);
249255
stateid = state.stateid();
250256
OpenState openState = new OpenState(client, owner, stateid, shareAccess, shareDeny);
@@ -256,12 +262,12 @@ public OpenRecord addOpen(NFS4Client client, StateOwner owner, Inode inode, int
256262
var openStateid = new stateid4(stateid.other, stateid.seqid);
257263

258264
// REVISIT: currently only read-delegations are supported
259-
if (wantReadDelegation && canDelegate) {
265+
if (wantReadDelegation && canDelegateRead) {
260266
// REVISIT: currently only read-delegations are supported
261-
stateid4 delegationStateid = client.createState(state.getStateOwner(), state).stateid();
267+
var delegationStateid = client.createState(state.getStateOwner(), state);
262268
delegations.computeIfAbsent(fileId, x -> new ArrayList<>(1))
263-
.add(new DelegationState(client, openStateid, delegationStateid, open_delegation_type4.OPEN_DELEGATE_READ));
264-
return new OpenRecord(openStateid, delegationStateid, true);
269+
.add(new DelegationState(client, delegationStateid, open_delegation_type4.OPEN_DELEGATE_READ));
270+
return new OpenRecord(openStateid, delegationStateid.stateid(), true);
265271
} else {
266272
//we need to return copy to avoid modification by concurrent opens
267273
return new OpenRecord(openStateid, null, false);
@@ -332,7 +338,7 @@ public stateid4 downgradeOpen(NFS4Client client, stateid4 stateid, Inode inode,
332338
* @param inode the inode of the delegated file.
333339
*/
334340
public void delegationReturn(NFS4Client client, stateid4 stateid, Inode inode)
335-
throws StaleException {
341+
throws ChimeraNFSException {
336342

337343
Opaque fileId = new Opaque(inode.getFileId());
338344
Lock lock = filesLock.get(fileId);
@@ -345,11 +351,12 @@ public void delegationReturn(NFS4Client client, stateid4 stateid, Inode inode)
345351
}
346352

347353
DelegationState delegation = fileDelegations.stream()
348-
.filter(d -> d.client().getId() == client.getId())
349-
.filter(d -> d.stateid().equals(stateid))
354+
.filter(d -> d.client().getId().equals(client.getId()))
355+
.filter(d -> d.delegationStateid().stateid().equals(stateid))
350356
.findFirst()
351357
.orElseThrow(StaleException::new);
352358

359+
delegation.delegationStateid().tryDispose();
353360
fileDelegations.remove(delegation);
354361
if (fileDelegations.isEmpty()) {
355362
delegations.remove(fileId);
@@ -392,22 +399,6 @@ void removeOpen(Inode inode, stateid4 stateid) {
392399
}
393400
}
394401

395-
var existingDelegations = delegations.get(fileId);
396-
if (existingDelegations != null) {
397-
Iterator<DelegationState> dsi = existingDelegations.iterator();
398-
while (dsi.hasNext()) {
399-
stateid4 os = dsi.next().openStateId();
400-
if (os.equals(stateid)) {
401-
dsi.remove();
402-
break;
403-
}
404-
}
405-
406-
if (existingDelegations.isEmpty()) {
407-
delegations.remove(fileId);
408-
}
409-
}
410-
411402
} finally {
412403
lock.unlock();
413404
}

core/src/test/java/org/dcache/nfs/v4/FileTrackerTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,24 +238,25 @@ public void shouldNotReadDelegation() throws Exception {
238238
@Test
239239
public void shouldReCallReadDelegationOnConflict() throws Exception {
240240

241-
NFS4Client client = createClient(sh);
241+
NFS4Client client1 = createClient(sh);
242+
NFS4Client client2 = createClient(sh);
242243

243-
StateOwner stateOwner1 = client.getOrCreateOwner("client1".getBytes(StandardCharsets.UTF_8), new seqid4(0));
244-
StateOwner stateOwner2 = client.getOrCreateOwner("client2".getBytes(StandardCharsets.UTF_8), new seqid4(0));
244+
StateOwner stateOwner1 = client1.getOrCreateOwner("client1".getBytes(StandardCharsets.UTF_8), new seqid4(0));
245+
StateOwner stateOwner2 = client2.getOrCreateOwner("client2".getBytes(StandardCharsets.UTF_8), new seqid4(0));
245246

246247
nfs_fh4 fh = generateFileHandle();
247248
Inode inode = Inode.forFile(fh.value);
248249

249-
var openRecord1 = tracker.addOpen(client, stateOwner1, inode, OPEN4_SHARE_ACCESS_READ | nfs4_prot.OPEN4_SHARE_ACCESS_WANT_READ_DELEG, 0);
250+
var openRecord1 = tracker.addOpen(client1, stateOwner1, inode, OPEN4_SHARE_ACCESS_READ | nfs4_prot.OPEN4_SHARE_ACCESS_WANT_READ_DELEG, 0);
250251
try {
251-
var openRecord2 = tracker.addOpen(client, stateOwner2, inode, OPEN4_SHARE_ACCESS_WRITE,
252+
var openRecord2 = tracker.addOpen(client2, stateOwner2, inode, OPEN4_SHARE_ACCESS_WRITE,
252253
0);
253254
fail("Delay exception expected");
254255
} catch (DelayException e) {
255256
// expected
256257
}
257258

258-
verify(client.getCB()).cbDelegationRecall(any(), any(), anyBoolean());
259+
verify(client1.getCB()).cbDelegationRecall(any(), any(), anyBoolean());
259260
}
260261

261262
@Test

0 commit comments

Comments
 (0)