Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Add FUNCTION KILL command. #1560

Merged
merged 4 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ enum RequestType {
BLMPop = 158;
XLen = 159;
Sort = 160;
FunctionKill = 161;
LSet = 165;
XDel = 166;
XRange = 167;
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub enum RequestType {
BLMPop = 158,
XLen = 159,
Sort = 160,
FunctionKill = 161,
LSet = 165,
XDel = 166,
XRange = 167,
Expand Down Expand Up @@ -356,6 +357,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::ExpireTime => RequestType::ExpireTime,
ProtobufRequestType::PExpireTime => RequestType::PExpireTime,
ProtobufRequestType::XLen => RequestType::XLen,
ProtobufRequestType::FunctionKill => RequestType::FunctionKill,
ProtobufRequestType::LSet => RequestType::LSet,
ProtobufRequestType::XDel => RequestType::XDel,
ProtobufRequestType::XRange => RequestType::XRange,
Expand Down Expand Up @@ -539,6 +541,7 @@ impl RequestType {
RequestType::ExpireTime => Some(cmd("EXPIRETIME")),
RequestType::PExpireTime => Some(cmd("PEXPIRETIME")),
RequestType::XLen => Some(cmd("XLEN")),
RequestType::FunctionKill => Some(get_two_word_command("FUNCTION", "KILL")),
RequestType::LSet => Some(cmd("LSET")),
RequestType::XDel => Some(cmd("XDEL")),
RequestType::XRange => Some(cmd("XRANGE")),
Expand Down
6 changes: 6 additions & 0 deletions java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionDelete;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionFlush;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionKill;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionList;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
Expand Down Expand Up @@ -277,4 +278,9 @@ public CompletableFuture<Boolean> copy(
}
return commandManager.submitNewCommand(Copy, arguments, this::handleBooleanResponse);
}

@Override
public CompletableFuture<String> functionKill() {
return commandManager.submitNewCommand(FunctionKill, new String[0], this::handleStringResponse);
}
}
12 changes: 12 additions & 0 deletions java/client/src/main/java/glide/api/RedisClusterClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionDelete;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionFlush;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionKill;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionList;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
Expand Down Expand Up @@ -575,4 +576,15 @@ public CompletableFuture<ClusterValue<Object>> fcall(
? ClusterValue.ofSingleValue(handleObjectOrNullResponse(response))
: ClusterValue.ofMultiValue(handleMapResponse(response)));
}

@Override
public CompletableFuture<String> functionKill() {
return commandManager.submitNewCommand(FunctionKill, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> functionKill(@NonNull Route route) {
return commandManager.submitNewCommand(
FunctionKill, new String[0], route, this::handleStringResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,37 @@ CompletableFuture<ClusterValue<Map<String, Object>[]>> functionList(
* }</pre>
*/
CompletableFuture<ClusterValue<Object>> fcall(String function, String[] arguments, Route route);

/**
* Kills a function that is currently executing.<br>
* <code>FUNCTION KILL</code> terminates read-only functions only.<br>
* The command will be routed to all primary nodes.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/docs/latest/commands/function-kill/">redis.io</a> for details.
* @return <code>OK</code> if function is terminated. Otherwise, throws an error.
* @example
* <pre>{@code
* String response = client.functionKill().get();
* assert response.equals("OK");
* }</pre>
*/
CompletableFuture<String> functionKill();

/**
* Kills a function that is currently executing.<br>
* <code>FUNCTION KILL</code> terminates read-only functions only.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/docs/latest/commands/function-kill/">redis.io</a> for details.
* @param route Specifies the routing configuration for the command. The client will route the
* command to the nodes defined by <code>route</code>.
* @return <code>OK</code> if function is terminated. Otherwise, throws an error.
* @example
* <pre>{@code
* String response = client.functionKill(RANDOM).get();
* assert response.equals("OK");
* }</pre>
*/
CompletableFuture<String> functionKill(Route route);
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,19 @@ public interface ScriptingAndFunctionsCommands {
* }</pre>
*/
CompletableFuture<Object> fcall(String function);

/**
* Kills a function that is currently executing.<br>
* <code>FUNCTION KILL</code> terminates read-only functions only.
*
* @since Redis 7.0 and above.
* @see <a href="https://redis.io/docs/latest/commands/function-kill/">redis.io</a> for details.
* @return <code>OK</code> if function is terminated. Otherwise, throws an error.
* @example
* <pre>{@code
* String response = client.functionKill().get();
* assert response.equals("OK");
* }</pre>
*/
CompletableFuture<String> functionKill();
}
22 changes: 22 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionDelete;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionFlush;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionKill;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionList;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.GeoAdd;
Expand Down Expand Up @@ -5226,6 +5227,27 @@ public void fcall_returns_success() {
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void functionKill_returns_success() {
// setup
String[] args = new String[0];
CompletableFuture<String> testResponse = new CompletableFuture<>();
testResponse.complete(OK);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(FunctionKill), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.functionKill();
String payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(OK, payload);
}

@SneakyThrows
@Test
public void bitcount_returns_success() {
Expand Down
43 changes: 43 additions & 0 deletions java/client/src/test/java/glide/api/RedisClusterClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.FlushAll;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionDelete;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionFlush;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionKill;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionList;
import static redis_request.RedisRequestOuterClass.RequestType.FunctionLoad;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
Expand Down Expand Up @@ -1543,4 +1544,46 @@ public void fcall_without_keys_and_with_route_returns_success() {
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void functionKill_returns_success() {
// setup
String[] args = new String[0];
CompletableFuture<String> testResponse = new CompletableFuture<>();
testResponse.complete(OK);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(FunctionKill), eq(args), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.functionKill();
String payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(OK, payload);
}

@SneakyThrows
@Test
public void functionKill_with_route_returns_success() {
// setup
String[] args = new String[0];
CompletableFuture<String> testResponse = new CompletableFuture<>();
testResponse.complete(OK);

// match on protobuf request
when(commandManager.<String>submitNewCommand(eq(FunctionKill), eq(args), eq(RANDOM), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<String> response = service.functionKill(RANDOM);
String payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(OK, payload);
}
}
32 changes: 32 additions & 0 deletions java/integTest/src/test/java/glide/TestUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,36 @@ public static String generateLuaLibCode(
}
return code.toString();
}

/**
* Create a lua lib with a RO function which runs an endless loop up to timeout sec.<br>
* Execution takes at least 5 sec regardless of the timeout configured.<br>
* If <code>readOnly</code> is <code>false</code>, function sets a dummy value to the first key
* given.
*/
public static String createLuaLibWithLongRunningFunction(
String libName, String funcName, int timeout, boolean readOnly) {
String code =
"#!lua name=$libName\n"
+ "local function $libName_$funcName(keys, args)\n"
+ " local started = tonumber(redis.pcall('time')[1])\n"
// fun fact - redis does no writes if 'no-writes' flag is set
+ " redis.pcall('set', keys[1], 42)\n"
+ " while (true) do\n"
+ " local now = tonumber(redis.pcall('time')[1])\n"
+ " if now > started + $timeout then\n"
+ " return 'Timed out $timeout sec'\n"
+ " end\n"
+ " end\n"
+ " return 'OK'\n"
+ "end\n"
+ "redis.register_function{\n"
+ "function_name='$funcName',\n"
+ "callback=$libName_$funcName,\n"
+ (readOnly ? "flags={ 'no-writes' }\n" : "")
+ "}";
return code.replace("$timeout", Integer.toString(timeout))
.replace("$funcName", funcName)
.replace("$libName", libName);
}
}
Loading
Loading