diff --git a/src/module/redis/command/module_redis_command_psetex.c b/src/module/redis/command/module_redis_command_psetex.c new file mode 100644 index 000000000..e5c30c917 --- /dev/null +++ b/src/module/redis/command/module_redis_command_psetex.c @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2018-2022 Vito Castellano + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD license. See the LICENSE file for details. + **/ + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "exttypes.h" +#include "log/log.h" +#include "clock.h" +#include "spinlock.h" +#include "xalloc.h" +#include "data_structures/small_circular_queue/small_circular_queue.h" +#include "data_structures/double_linked_list/double_linked_list.h" +#include "data_structures/queue_mpmc/queue_mpmc.h" +#include "slab_allocator.h" +#include "data_structures/hashtable/mcmp/hashtable.h" +#include "data_structures/hashtable/mcmp/hashtable_op_set.h" +#include "data_structures/hashtable/spsc/hashtable_spsc.h" +#include "protocol/redis/protocol_redis.h" +#include "protocol/redis/protocol_redis_reader.h" +#include "protocol/redis/protocol_redis_writer.h" +#include "module/module.h" +#include "network/io/network_io_common.h" +#include "config.h" +#include "fiber.h" +#include "network/channel/network_channel.h" +#include "storage/io/storage_io_common.h" +#include "storage/channel/storage_channel.h" +#include "storage/db/storage_db.h" +#include "module/redis/module_redis.h" +#include "module/redis/module_redis_connection.h" +#include "module/redis/module_redis_command.h" +#include "network/network.h" +#include "worker/worker_stats.h" +#include "worker/worker_context.h" + +#define TAG "module_redis_command_psetex" + +MODULE_REDIS_COMMAND_FUNCPTR_COMMAND_END(psetex) { + bool return_res = false; + bool key_and_value_owned = false; + module_redis_command_psetex_context_t *context = connection_context->command.context; + + if (context->milliseconds.value <= 0) { + return_res = module_redis_connection_error_message_printf_noncritical( + connection_context, + "ERR invalid expire time in 'psetex' command"); + goto end; + } + + storage_db_expiry_time_ms_t expiry_time_ms = + clock_realtime_coarse_int64_ms() + (int64_t)context->milliseconds.value; + + if (unlikely(!storage_db_op_set( + connection_context->db, + context->key.value.key, + context->key.value.length, + context->value.value.chunk_sequence, + expiry_time_ms))) { + return_res = module_redis_connection_error_message_printf_noncritical( + connection_context, + "ERR psetex failed"); + goto end; + } + + key_and_value_owned = true; + return_res = module_redis_connection_send_ok(connection_context); + +end: + if (likely(key_and_value_owned)) { + // Mark both the key and the chunk_sequence as NULL as the storage db now owns them, we don't want them to be + // automatically freed at the end of the execution, especially the key as the hashtable might not need to hold + // a reference to it, it might have already been freed + context->key.value.key = NULL; + context->value.value.chunk_sequence = NULL; + } + + return return_res; +} diff --git a/tests/unit_tests/modules/redis/command/test-modules-redis-command-psetex.cpp b/tests/unit_tests/modules/redis/command/test-modules-redis-command-psetex.cpp new file mode 100644 index 000000000..776efac42 --- /dev/null +++ b/tests/unit_tests/modules/redis/command/test-modules-redis-command-psetex.cpp @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2018-2022 Vito Castellano + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD license. See the LICENSE file for details. + **/ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "clock.h" +#include "exttypes.h" +#include "spinlock.h" +#include "data_structures/small_circular_queue/small_circular_queue.h" +#include "data_structures/double_linked_list/double_linked_list.h" +#include "data_structures/hashtable/mcmp/hashtable.h" +#include "config.h" +#include "fiber.h" +#include "worker/worker_stats.h" +#include "worker/worker_context.h" +#include "signal_handler_thread.h" +#include "storage/io/storage_io_common.h" +#include "storage/channel/storage_channel.h" +#include "storage/db/storage_db.h" + +#include "program.h" + +#include "test-modules-redis-command-fixture.hpp" + +#pragma GCC diagnostic ignored "-Wwrite-strings" + +TEST_CASE_METHOD(TestModulesRedisCommandFixture, "Redis - command - PSETEX", "[redis][command][PSETEX]") { + SECTION("Missing parameters - key, milliseconds and value") { + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX"}, + "-ERR wrong number of arguments for 'PSETEX' command\r\n")); + } + + SECTION("Missing parameters - value") { + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX", "a_key", "100"}, + "-ERR wrong number of arguments for 'PSETEX' command\r\n")); + } + + SECTION("Too many parameters - one extra parameter") { + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX", "a_key", "100", "b_value", "extra parameter"}, + "-ERR syntax error\r\n")); + } + + SECTION("Zero value as expire") { + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX", "a_key", "0", "b_value"}, + "-ERR invalid expire time in 'psetex' command\r\n")); + } + + SECTION("New key - expire in 500ms") { + char *key = "a_key"; + char *value = "b_value"; + config_module_network_timeout.read_ms = 1000; + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX", key, "500", value}, + "+OK\r\n")); + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"GET", key}, + "$7\r\nb_value\r\n")); + + // Wait for 600 ms and try to get the value after the expiration + usleep((500 + 100) * 1000); + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"GET", key}, + "$-1\r\n")); + + storage_db_entry_index_t *entry_index = storage_db_get_entry_index(db, key, strlen(key)); + REQUIRE(entry_index == NULL); + } + + SECTION("New key - expire in 1s") { + char *key = "a_key"; + char *value = "b_value"; + config_module_network_timeout.read_ms = 2000; + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"PSETEX", key, "1000", value}, + "+OK\r\n")); + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"GET", key}, + "$7\r\nb_value\r\n")); + + // Wait for 1100 ms and try to get the value after the expiration + usleep((1000 + 100) * 1000); + + REQUIRE(send_recv_resp_command_text( + client_fd, + std::vector{"GET", key}, + "$-1\r\n")); + + storage_db_entry_index_t *entry_index = storage_db_get_entry_index(db, key, strlen(key)); + REQUIRE(entry_index == NULL); + } +} \ No newline at end of file