Skip to content
Open
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
108 changes: 108 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
%{
configs: [
%{
name: "default",
files: %{
included: [
"lib/"
],
excluded: [
~r"/_build/",
~r"/deps/",
~r"/node_modules/"
]
},
plugins: [],
requires: [],
strict: true,
parse_timeout: 5000,
color: true,
checks: %{
enabled: [
#
# Consistency
#
{Credo.Check.Consistency.ExceptionNames, []},
{Credo.Check.Consistency.LineEndings, []},
{Credo.Check.Consistency.ParameterPatternMatching, []},
{Credo.Check.Consistency.SpaceAroundOperators, []},
{Credo.Check.Consistency.SpaceInParentheses, []},
{Credo.Check.Consistency.TabsOrSpaces, []},

#
# Design
#
{Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 1]},
{Credo.Check.Design.TagTODO, [exit_status: 0]},
{Credo.Check.Design.TagFIXME, []},

#
# Readability
#
{Credo.Check.Readability.AliasOrder, []},
{Credo.Check.Readability.FunctionNames, []},
{Credo.Check.Readability.LargeNumbers, []},
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
{Credo.Check.Readability.ModuleAttributeNames, []},
{Credo.Check.Readability.ModuleDoc, [priority: :low]},
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.PredicateFunctionNames, []},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
{Credo.Check.Readability.SpaceAfterCommas, []},
{Credo.Check.Readability.StringSigils, []},
{Credo.Check.Readability.TrailingBlankLine, []},
{Credo.Check.Readability.TrailingWhiteSpace, []},
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},

#
# Refactoring
#
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 15]},
{Credo.Check.Refactor.FunctionArity, [max_arity: 6]},
{Credo.Check.Refactor.LongQuoteBlocks, [max_line_count: 500]},
{Credo.Check.Refactor.MapJoin, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, [max_nesting: 4]},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},

#
# Warnings
#
{Credo.Check.Warning.BoolOperationOnSameValues, []},
{Credo.Check.Warning.Dbg, []},
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
{Credo.Check.Warning.OperationOnSameValues, []},
{Credo.Check.Warning.OperationWithConstantResult, []},
{Credo.Check.Warning.RaiseInsideRescue, []},
{Credo.Check.Warning.SpecWithStruct, []},
{Credo.Check.Warning.UnsafeExec, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Warning.UnusedFileOperation, []},
{Credo.Check.Warning.UnusedKeywordOperation, []},
{Credo.Check.Warning.UnusedListOperation, []},
{Credo.Check.Warning.UnusedPathOperation, []},
{Credo.Check.Warning.UnusedRegexOperation, []},
{Credo.Check.Warning.UnusedStringOperation, []},
{Credo.Check.Warning.UnusedTupleOperation, []}
],
disabled: [
# Disabled: macros generate code that legitimately doesn't use all params
{Credo.Check.Warning.UnusedFunctionReturnHelper, []},
# Disabled: pipe vs no pipe is a style choice in our macros
{Credo.Check.Refactor.PipeChainStart, []}
]
}
}
]
}
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
- run: mix deps.compile
- run: mix format --check-formatted
- run: mix deps.unlock --check-unused
- run: mix credo --strict

test:
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -66,5 +67,5 @@ jobs:
restore-keys: ${{ matrix.os }}-otp_${{ matrix.otp }}-elixir_${{ matrix.elixir }}-mix_
- run: mix deps.get --only test
- run: mix deps.compile
- run: mix compile
- run: mix compile --warnings-as-errors
- run: mix test
1 change: 1 addition & 0 deletions .mcp.json
25 changes: 24 additions & 1 deletion lib/redlock.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,26 @@ defmodule Redlock do

end

Or you can use `transaction` function
You can optionally assign a custom value (for example, your node name, hostname, or any unique identifier per process) to the lock.
This value is stored as the lock value in Redis, making it easy to identify and debug which node or process currently owns the lock.

resource = "example_key:#{user_id}"
lock_exp_sec = 10
value = Atom.to_string(Node.self())

case Redlock.lock(resource, lock_exp_sec, value) do

{:ok, mutex} ->
# some other code which write and read on RDBMS, KVS or other storage
# call unlock finally
Redlock.unlock(resource, mutex)

:error ->
Logger.error "failed to lock resource. maybe redis connection got trouble."
{:error, :system_error}
end

Or you can use the `transaction` function:

def my_function() do
# do something, and return {:ok, :my_result} or {:error, :my_error}
Expand Down Expand Up @@ -162,6 +181,10 @@ defmodule Redlock do
Redlock.Executor.lock(resource, ttl)
end

def lock(resource, ttl, value) do
Redlock.Executor.lock(resource, ttl, value)
end

def unlock(resource, value) do
Redlock.Executor.unlock(resource, value)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/redlock/executor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ defmodule Redlock.Executor do
do_lock(resource, ttl, random_value(), 0, FastGlobal.get(:redlock_conf))
end

def lock(resource, ttl, value) do
do_lock(resource, ttl, value, 0, FastGlobal.get(:redlock_conf))
end

def unlock(resource, value) do
config = FastGlobal.get(:redlock_conf)

Expand Down
11 changes: 10 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Redlock.Mixfile do
version: @version,
package: package(),
start_permanent: Mix.env() == :prod,
elixirc_options: [warnings_as_errors: true],
aliases: aliases(),
deps: deps(),
docs: docs()
]
Expand All @@ -28,7 +30,8 @@ defmodule Redlock.Mixfile do
{:redix, "~> 1.3"},
{:poolboy, "~> 1.5"},
{:fastglobal, "~> 1.0.0"},
{:ex_hash_ring, "~> 3.0"}
{:ex_hash_ring, "~> 3.0"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end

Expand All @@ -48,6 +51,12 @@ defmodule Redlock.Mixfile do
]
end

defp aliases do
[
precommit: ["compile", "credo --strict", "deps.unlock --unused", "format", "test"]
]
end

defp package() do
[
description: "Redlock (Redis Distributed Lock) implementation",
Expand Down
18 changes: 18 additions & 0 deletions test/redlock_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,22 @@ defmodule RedlockTest do
# extending a lock that is not held should fail
assert Redlock.extend("not-locked", "not-mutex", 10) == :error
end

test "lock with custom value" do
key = "with_value"
value = "node-12345"

# Acquire lock with custom value
assert {:ok, ^value} = Redlock.lock(key, 10, value)

# Attempt to lock again with different value should fail
assert :error == Redlock.lock(key, 10, "node-other")

# Release lock with value
assert :ok == Redlock.unlock(key, value)

# Now should be able to acquire again
assert {:ok, ^value} = Redlock.lock(key, 10, value)
assert :ok == Redlock.unlock(key, value)
end
end