Skip to content

[WIP]Add tryLock REST API for distributed lock#1509

Closed
mgao0 wants to merge 1 commit into
apache:masterfrom
mgao0:lock
Closed

[WIP]Add tryLock REST API for distributed lock#1509
mgao0 wants to merge 1 commit into
apache:masterfrom
mgao0:lock

Conversation

@mgao0
Copy link
Copy Markdown
Contributor

@mgao0 mgao0 commented Nov 3, 2020

Issues

  • My PR addresses the following Helix issues and references them in the PR description:

(#200 - Link your issue number here: You can write "Fixes #XXX". Please use the proper keyword so that the issue gets closed automatically. See https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue
Any of the following keywords can be used: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved)

Description

  • Here are some details about my PR, including screenshots of any UI changes:

(Write a concise description including what, why, how)

Tests

  • The following tests are written for this issue:

(List the names of added unit/integration tests)

  • The following is the result of the "mvn test" command on the appropriate module:

(Before CI test pass, please copy & paste the result of "mvn test")

Documentation (Optional)

  • In case of new functionality, my PR adds documentation in the following wiki page:

(Link the GitHub wiki you added)

Commits

  • My commits all reference appropriate Apache Helix GitHub issues in their subject lines. In addition, my commits follow the guidelines from "How to write a good git commit message":
    1. Subject is separated from body by a blank line
    2. Subject is limited to 50 characters (not including Jira issue reference)
    3. Subject does not end with a period
    4. Subject uses the imperative mood ("add", not "adding")
    5. Body wraps at 72 characters
    6. Body explains "what" and "why", not "how"

Code Quality

  • My diff has been formatted using helix-style.xml
    (helix-style-intellij.xml if IntelliJ IDE is used)

Comment on lines +213 to +220
String lockPath = null;
if (_lockScope != null) {
lockPath = _lockScope.getPath();
}

if (_lockPath != null) {
lockPath = _lockPath;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can understand the logic. But it seemed not be a good readable code.

If we think lockPath is necessary:

if (_lockScope != null) {
_lockPath = _lockScope.getPath();
}

Comment on lines +45 to +52
private static final String ZK_ADDR = "zkAddress";
private static final String LOCK_PATH = "lockPath";
private static final String LOCK_TIMEOUT = "lockTimeout";
private static final String USER_ID = "userId";
private static final String LOCK_MSG = "lockMsg";
private static final String LOCK_SCOPE_PROPERTY = "property";
private static final String PATH_KEYS = "pathKeys";
private static final String PATH_KEY_DELIMITER = "\\|";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create a constant class to hold them.

@ResponseMetered(name = HttpConstants.WRITE_REQUEST)
@Timed(name = HttpConstants.WRITE_REQUEST)
@POST
@Path("lock")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the URI "xxxx/locks/lock" ? Shall we call it "acquireLock"?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the URI "xxxx/locks/lock" ? Shall we call it "acquireLock"?
Write

In the REST API design, we don't expect a verb to represent a resource in the endpoint: the method POST/PUT/DELETE already tells the purpose.

@mgao0 Is this request "idempotent"? If one calls this endpoint N times with the same payload, will there be N locks, or just 1 lock?

@POST
@Path("lock")
public Response tryLock(String content) {
System.out.println("Payload:\n" + content);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to log.

"Lock path is not found or lock scope could not be generated, please examine the request.");
}

return result ? OK(JSONRepresentation(lock)) : serverError(String.format(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will not see the serverError part, right? Because it already returns badrequst in previous catch.

LockInfo lockInfo;
try {
// Parse payload data
ZNRecord lockContent = toZNRecord(content);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a good idea to let input content to be ZNRecord. User has to input "simpleFields, listFields, mapFields" even they dont use it. I would suggest to use plain json format.

private String _userId;
private long _timeout;
private String _lockMsg;
private String _lockPath;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if lockPath is confilct the lockPath in lockScope.

ZooKeeperAccessor.class.getPackage().getName(),
MetadataStoreDirectoryAccessor.class.getPackage().getName()
}),
new String[]{AbstractHelixResource.class.getPackage().getName(), NamespacesAccessor.class
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format is a bit weird. Can you revert them to previous one?

// Perform try lock
boolean result;
try {
result = lock.tryLock();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reminds me a question from our users. They wondered whether they can distinguish between zk error and lock owned by others. From our current implementation of zkclient, I think the answer is no, they are both returned as zero. could you double check that part? If that's the case, you may want to comment somewhere.

}
}

@ResponseMetered(name = HttpConstants.WRITE_REQUEST)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with Java API, this one should be tryLock.
Actually I suggest to think over all APIs before finalizing a specific one. E.g. for unlocking a lock or getting lock information, etc. I feel unlock should be similar to this, as an action, instead of DELETE operation. For get lock information, what information will you provide to users?

if (znRecord == null) {
return null;
}
// Parse user iod
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo

throw new Exception("Please check the timeout value input, could not convert to long.");
}

// Parse lock message
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple comments like this, which I think the code is self explanatory. You may just remove them for concise code.

pathKeys.add(participantName);
}
}
default:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your previous try catch already excluded all other cases, so you don't need default here.

Comment on lines +179 to +181
String CLUSTER = "cluster";
String RESOURCE = "resource";
String PARTICIPANT = "participant";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be defined as final static in the beginning too.

private static final String LOCK_MSG = "lockMsg";
private static final String LOCK_SCOPE_PROPERTY = "property";
private static final String PATH_KEYS = "pathKeys";
private static final String PATH_KEY_DELIMITER = "\\|";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definition doesn't look right. btw, it is not used anywhere.

}

@Test(dependsOnMethods = "testLockAccessorWithHelixLockScope")
public void testLockAccessorWithUnknownScope() throws JsonProcessingException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe rename it to general scope etc.

<artifact name="snakeyaml" m:classifier="sources" ext="jar"/>
</dependency>
<dependency org="org.apache.helix" name="helix-core" rev="1.0.2-SNAPSHOT" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
<dependency org="org.apache.helix" name="helix-lock" rev="1.0.2-SNAPSHOT" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: fix indentation

@Path("lock")
public Response tryLock(String content) {
System.out.println("Payload:\n" + content);
ZKDistributedNonblockingLock lock;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: probably better if we declare it as an interface?

// Build a lock object
lock = new ZKDistributedNonblockingLock.Builder().setLockMsg(lockInfo.getMessage())
.setUserId(lockInfo.getOwner()).setTimeout(lockInfo.getTimeout())
.setZkAddress(lockLocator.getZkAddress()).setLockPath(lockLocator.getLockPath()).build();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be careful here - what would the zk address be set as if the user was using it on multi-zk mode?

.setUserId(lockInfo.getOwner()).setTimeout(lockInfo.getTimeout())
.setZkAddress(lockLocator.getZkAddress()).setLockPath(lockLocator.getLockPath()).build();
} catch (Exception e) {
return badRequest(e.getMessage());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log this as well?

Comment on lines +111 to +112
return badRequest(
"Lock path is not found or lock scope could not be generated, please examine the request.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We need to let the caller know what the request was. Consider embedding that information in the exception message.
  2. Should we add a server-side exception log here as well?

Comment on lines +121 to +123
if (znRecord == null) {
return null;
}
Copy link
Copy Markdown
Contributor

@narendly narendly Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is a good pattern to use. Usually these functions expect a non-null values as parameters. Should you do a null check on the zn record before calling parseLockLocator()? This is a debatable point, but maybe worth giving some consideration.

Comment on lines +158 to +160
if (znRecord == null) {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same - should we do a null check on znrecord in the beginning?

try {
lockTimeout = Long.parseLong(znRecord.getSimpleField(LOCK_TIMEOUT));
} catch (NumberFormatException e) {
throw new Exception("Please check the timeout value input, could not convert to long.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's let the caller know what the value is?

Comment on lines +179 to +181
String CLUSTER = "cluster";
String RESOURCE = "resource";
String PARTICIPANT = "participant";
Copy link
Copy Markdown
Contributor

@narendly narendly Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we already have the enum for it. why can't we just use the enum instead of defining new static constants?

Either add a method in the enum that returns its lower-case equivalent or just do a enum.getString().toLowerCase().

Comment on lines +184 to +190
try {
property = HelixLockScope.LockScopeProperty.valueOf(lockScopeProperty);
} catch (IllegalArgumentException e) {
throw new Exception(String.format(
"User requested lock scope property %s, but this property does not exist in HelixLockScope.",
lockScopeProperty));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a shorter way of doing this. Try googling? It looks like Enums.getIfPresent(Blah.class, "A")...

}
}
default:
// Not gonna happen
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like a good example of a JavaDoc comment. Let's try to make it more descriptive or remove it altogether.

}
if (pathKeys.isEmpty()) {
throw new Exception(
"Not enough path keys are provided to construct the lock path. Please check the request.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let the user know what the request was. Also do we need to log it on the server side?

@jiajunwang
Copy link
Copy Markdown
Contributor

Close due to inactive.

@jiajunwang jiajunwang closed this May 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants