diff --git a/Makefile b/Makefile index 75302b2..39d5fef 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,41 @@ -REBAR = $(shell command -v rebar || echo ./rebar) -DEPS_PLT=./.deps_plt -DEPS=erts kernel stdlib +REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3 -.PHONY: all get-deps compile clean dialyze xref +ifeq ($(wildcard rebar3),rebar3) + REBAR3 = $(CURDIR)/rebar3 +endif -all: get-deps compile +REBAR3 ?= $(shell test -e `which rebar3` 2>/dev/null && which rebar3 || echo "./rebar3") -get-deps: - @$(REBAR) get-deps +ifeq ($(REBAR3),) + REBAR3 = $(CURDIR)/rebar3 +endif -compile: - @$(REBAR) compile +.PHONY: deps build clean dialyzer xref doc test publish -test: compile - $(REBAR) eunit skip_deps=true +all: build + +build: $(REBAR3) + @$(REBAR3) compile + +$(REBAR3): + wget $(REBAR3_URL) || curl -Lo rebar3 $(REBAR3_URL) + @chmod a+x rebar3 clean: - @$(REBAR) clean - -$(DEPS_PLT): - @echo Building $(DEPS_PLT) - dialyzer --build_plt \ - --output_plt $(DEPS_PLT) \ - --apps $(DEPS) -#-r deps \ - -dialyze: compile $(DEPS_PLT) - dialyzer --fullpath \ - --src src \ - -Wunmatched_returns \ - -Werror_handling \ - -Wrace_conditions \ - -Wunderspecs \ - -r ebin \ - --plt $(DEPS_PLT) + @$(REBAR3) clean + +dialyzer: + @$(REBAR3) dialyzer xref: - @$(REBAR) xref + @$(REBAR3) xref -doc: compile +test: + @$(REBAR3) eunit + +doc: build + ./scripts/hackish_inject_version_in_docs.sh ./scripts/hackish_make_docs.sh + +publish: + @$(REBAR3) as publish hex publish diff --git a/README.md b/README.md index c96df8f..a941744 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ Copyright (c) 2016 Guilherme Andrade -__Version:__ 1.0.3 +__Version:__ 1.0.3-1-gae0fecf __Authors:__ Guilherme Andrade ([`nlocks(at)gandrade(dot)net`](mailto:nlocks(at)gandrade(dot)net)). `nlocks`: Native spinlocks for Erlang + --------- An experiment on Erlang native spinlocks: @@ -36,7 +37,6 @@ end, ### Brutal kills and unreleased locks ### - Hackish solution. Other than setting up some sort of monitor in the Erlang land, I found no practical way to deal with these other than making use of _ownership_ objects, references to which should never leave the process under which they were created; the release therefore becomes dependent on the garbage collector calling their destructor, but this also makes it more convenient for regularly terminated processes that forget to clean up. @@ -77,7 +77,6 @@ struct Lock { ### The spinning ### - * It uses C++ 11 [compare_exchange_weak](http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) * For each attempt, a new NIF call is scheduled; this brings some overhead to the table but it's crucial to play nice with the VM * If the timeout is other than 'infinity', for each attempt it compares the deadline against the current [steady_clock](http://en.cppreference.com/w/cpp/chrono/steady_clock) value; this feels excessive and might hurt performance. diff --git a/cpp_src/Makefile b/cpp_src/Makefile index 944249f..75e1e61 100644 --- a/cpp_src/Makefile +++ b/cpp_src/Makefile @@ -6,7 +6,7 @@ endif CXX=g++ RM=rm -f -INC=-I$(ERL_INCLUDE) +INC= CPPFLAGS=-std=c++11 -Wall -Wextra -Wpedantic -fPIC -O2 $(INC) LDLIBS= diff --git a/doc/utf8/README.md b/doc/README.md similarity index 99% rename from doc/utf8/README.md rename to doc/README.md index 965e9b8..7bd18bd 100644 --- a/doc/utf8/README.md +++ b/doc/README.md @@ -4,11 +4,12 @@ Copyright (c) 2016 Guilherme Andrade -__Version:__ 1.0.3 +__Version:__ 1.0.3-1-gae0fecf __Authors:__ Guilherme Andrade ([`nlocks(at)gandrade(dot)net`](mailto:nlocks(at)gandrade(dot)net)). `nlocks`: Native spinlocks for Erlang + --------- An experiment on Erlang native spinlocks: @@ -35,7 +36,6 @@ end, ### Brutal kills and unreleased locks ### - Hackish solution. Other than setting up some sort of monitor in the Erlang land, I found no practical way to deal with these other than making use of _ownership_ objects, references to which should never leave the process under which they were created; the release therefore becomes dependent on the garbage collector calling their destructor, but this also makes it more convenient for regularly terminated processes that forget to clean up. @@ -76,7 +76,6 @@ struct Lock { ### The spinning ### - * It uses C++ 11 [compare_exchange_weak](http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) * For each attempt, a new NIF call is scheduled; this brings some overhead to the table but it's crucial to play nice with the VM * If the timeout is other than 'infinity', for each attempt it compares the deadline against the current [steady_clock](http://en.cppreference.com/w/cpp/chrono/steady_clock) value; this feels excessive and might hurt performance. diff --git a/doc/edoc-info b/doc/edoc-info index 38f3977..ee81130 100644 --- a/doc/edoc-info +++ b/doc/edoc-info @@ -1,3 +1,3 @@ %% encoding: UTF-8 -{packages,[]}. +{application,nlocks}. {modules,[nlocks]}. diff --git a/doc/index.html b/doc/index.html index 047b9dc..434eaf6 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1,7 +1,7 @@ -Overview +The nlocks application diff --git a/doc/modules-frame.html b/doc/modules-frame.html index 32ceccc..7dda2d8 100644 --- a/doc/modules-frame.html +++ b/doc/modules-frame.html @@ -1,7 +1,7 @@ -Overview +The nlocks application diff --git a/doc/nlocks.html b/doc/nlocks.html index af762cf..aa2d218 100644 --- a/doc/nlocks.html +++ b/doc/nlocks.html @@ -76,6 +76,6 @@

transaction/3


-

Generated by EDoc, Jun 12 2016, 19:29:27.

+

Generated by EDoc, May 1 2017, 02:07:22.

diff --git a/doc/nlocks.md b/doc/nlocks.md index f4fd02d..0b0bcd1 100644 --- a/doc/nlocks.md +++ b/doc/nlocks.md @@ -5,8 +5,6 @@ * [Function Index](#index) * [Function Details](#functions) - - ## Data Types ## @@ -33,12 +31,10 @@ __abstract datatype__: `ownership()` ### trx_fun() ### -

 trx_fun() = fun(() -> term())
 
- ## Function Index ## @@ -55,90 +51,62 @@ trx_fun() = fun(() -> term()) ### acquire_ownership/1 ### -

 acquire_ownership(Lock::lock()) -> {ok, ownership()} | {error, timeout}
 
- -

- - +
### acquire_ownership/2 ### -

 acquire_ownership(Lock::lock(), Timeout::timeout()) -> {ok, ownership()} | {error, timeout}
 
- -

- - +
### info/0 ### -

 info() -> [{allocated_locks | allocated_ownerships | acquired_locks | contention, non_neg_integer()} | {has_lockfree_counters | has_lockfree_ownership, boolean()}, ...]
 
- -

- - +
### new/0 ### -

 new() -> lock()
 
- -

- - +
### release_ownership/1 ### -

 release_ownership(Ownership::ownership()) -> ok | {error, not_allowed} | {error, already_released}
 
- -

- - +
### transaction/2 ### -

 transaction(Lock::lock(), Fun::trx_fun()) -> {ok, FunResult::term()} | {error, timeout}
 
- -

- - +
### transaction/3 ### -

 transaction(Lock::lock(), Fun::trx_fun(), Timeout::timeout()) -> {ok, FunResult::term()} | {error, timeout}
 
- -

- - +
diff --git a/doc/overview-summary.html b/doc/overview-summary.html index 4b58df7..f09ca8a 100644 --- a/doc/overview-summary.html +++ b/doc/overview-summary.html @@ -9,7 +9,7 @@

nlocks

Copyright © 2016 Guilherme Andrade

-

Version: 1.0.3

+

Version: 1.0.3-1-gae0fecf

Authors: Guilherme Andrade (nlocks(at)gandrade(dot)net).

nlocks: Native spinlocks for Erlang

@@ -88,6 +88,6 @@

Building


-

Generated by EDoc, Jun 12 2016, 19:29:27.

+

Generated by EDoc, May 1 2017, 02:07:22.

diff --git a/doc/overview.edoc b/doc/overview.edoc index 228fa15..dc4b3ce 100644 --- a/doc/overview.edoc +++ b/doc/overview.edoc @@ -1,6 +1,6 @@ @author Guilherme Andrade @copyright 2016 Guilherme Andrade -@version 1.0.3 +@version 1.0.3-1-gae0fecf @title nlocks @doc `nlocks': Native spinlocks for Erlang diff --git a/doc/packages-frame.html b/doc/packages-frame.html deleted file mode 100644 index 4193cbe..0000000 --- a/doc/packages-frame.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -Overview - - - -

Packages

-
- - \ No newline at end of file diff --git a/doc/stylesheet.css b/doc/stylesheet.css index e426a90..ab170c0 100644 --- a/doc/stylesheet.css +++ b/doc/stylesheet.css @@ -27,10 +27,10 @@ div.spec { margin-left: 2em; background-color: #eeeeee; } -a.module,a.package { +a.module { text-decoration:none } -a.module:hover,a.package:hover { +a.module:hover { background-color: #eeeeee; } ul.definitions { diff --git a/mix.exs b/mix.exs deleted file mode 100644 index f442a66..0000000 --- a/mix.exs +++ /dev/null @@ -1,44 +0,0 @@ -defmodule Nlocks.Mixfile do - use Mix.Project - - @version File.read!("VERSION") |> String.strip - - def project do - [app: :nlocks, - version: @version, - description: "Native spinlocks for Erlang", - compilers: [:make, :erlang, :app], - aliases: aliases, - package: package] - end - - defp aliases do - [clean: ["clean", "clean.make"]] - end - - defp package do - [files: ~w(cpp_src priv src rebar.config README.md LICENSE), - contributors: ["Guilherme Andrade"], - licenses: ["MIT"], - links: %{"GitHub" => "https://github.com/g-andrade/nlocks"}] - end -end - -# based on https://spin.atomicobject.com/2015/03/16/elixir-native-interoperability-ports-vs-nifs/ -defmodule Mix.Tasks.Compile.Make do - @shortdoc "Compiles helper in cpp_src" - def run(_) do - {result, _error_code} = System.cmd("make", ['-C', 'cpp_src/'], stderr_to_stdout: true) - Mix.shell.info result - :ok - end -end - -defmodule Mix.Tasks.Clean.Make do - @shortdoc "Cleans helper in c_src" - def run(_) do - {result, _error_code} = System.cmd("make", ['-C', 'cpp_src/', 'clean'], stderr_to_stdout: true) - Mix.shell.info result - :ok - end -end diff --git a/overview.edoc b/overview.edoc new file mode 100644 index 0000000..dc4b3ce --- /dev/null +++ b/overview.edoc @@ -0,0 +1,78 @@ +@author Guilherme Andrade +@copyright 2016 Guilherme Andrade +@version 1.0.3-1-gae0fecf +@title nlocks +@doc `nlocks': Native spinlocks for Erlang + +
+ +An experiment on Erlang native spinlocks: +* Able to guarantee exclusive access to shared resources +* Not dependent on a single process (no flooded mailbox as a bottleneck) +* Able to deal with brutally killed processes that still held unreleased locks (more below) + +
+Lock = nlocks:new(),
+Transaction = fun() ->
+    io:format("hello from ~p at ~p~n", [self(), os:timestamp()]),
+    timer:sleep(100)
+end,
+
+[spawn(fun () -> nlocks:transaction(Lock, Transaction) end) || _ <- lists:seq(1, 4)].
+% hello from <0.66.0> at {1465,509871,454813}
+% hello from <0.69.0> at {1465,509871,555028}
+% hello from <0.67.0> at {1465,509871,656021}
+% hello from <0.68.0> at {1465,509871,757031}
+
+ +== Brutal kills and unreleased locks == +Hackish solution. Other than setting up some sort of monitor in the Erlang land, I found no practical way to deal with these other than making use of _ownership_ objects, references to which should never leave the process under which they were created; the release therefore becomes dependent on the garbage collector calling their destructor, but this also makes it more convenient for regularly terminated processes that forget to clean up. + +== Ownership objects == +
+struct Ownership {
+    ERL_NIF_TERM pid;
+    Lock** lockResource;
+};
+
+ +* They're created for lock acquisition in order to deal with the above problem +* They should never leave their owning process +* On successful acquisition, they link themselves to the lock and force it to live as long as they live +* They guarantee only the lock owner can release the lock +* No explicit destruction needed + +== Lock objects == +
+struct Lock {
+    std::atomic<Ownership*> ownership;
+};
+
+ +* They keep an atomic pointer either set to null (free) or to the ownership object's address (locked) +* Can be shared between local processes through messages, ETS tables, etc. +* No explicit destruction needed + +== The spinning == +* It uses C++ 11 compare_exchange_weak +* For each attempt, a new NIF call is scheduled; this brings some overhead to the table but it's crucial to play nice with the VM +* If the timeout is other than 'infinity', for each attempt it compares the deadline against the current steady_clock value; this feels excessive and might hurt performance. + +== Internal metrics == +
+% nlocks:info()
+[{allocated_locks,2},
+ {allocated_ownerships,6},
+ {acquired_locks,1},
+ {contention,5}, % amount of processes attempting lock acquisition
+ {has_lockfree_counters,true},
+ {has_lockfree_ownership,true}]
+
+ +== Building == +The ERTS headers are required; if they're not available on a global include path, 'ERL_INCLUDE' can be explicitly defined. +
+% Example path
+ERL_INCLUDE=/opt/kerl/17.5/usr/include rebar compile
+
+The NIF shared object will be dumped into the priv/ directory. diff --git a/rebar b/rebar deleted file mode 100755 index 98e6469..0000000 Binary files a/rebar and /dev/null differ diff --git a/rebar.config b/rebar.config index f13f074..9cd3312 100644 --- a/rebar.config +++ b/rebar.config @@ -1,39 +1,49 @@ -{erl_opts, [ - warn_export_all, - warn_export_vars, - warn_missing_spec, - warn_obsolete_guard, - warn_shadow_vars, - warn_unused_import, - warnings_as_errors - ]}. +{pre_hooks, + [{"(linux|darwin|solaris)", compile, "make -C cpp_src"}, + {"(freebsd)", compile, "gmake -C cpp_src"}]}. -{pre_hooks, [ - {compile, "make -C cpp_src/"} - ]}. +{post_hooks, + [{"(linux|darwin|solaris)", clean, "make -C cpp_src clean"}, + {"(freebsd)", clean, "gmake -C cpp_src clean"}]}. -{post_hooks, [ - {clean, "make -C cpp_src/ clean"} - ]}. +{erl_opts, + [%bin_opt_info, + debug_info, + warn_export_all, + warn_export_vars, + warn_missing_spec, + warn_obsolete_guards, + warn_shadow_vars, + warn_unused_import, + warnings_as_errors]}. -{deps_dir, "deps"}. -{deps, [ - %{edown, ".*", {git, "git://github.com/esl/edown.git", {tag, "0.4"}}} - ]}. +{xref_checks, + [undefined_function_calls, + undefined_functions, + locals_not_used, + exports_not_used, + deprecated_function_calls, + deprecated_functions]}. -{edoc_opts, [ - %{doclet, edown_doclet} - ]}. +{dialyzer, + [{plt_include_all_deps, true}, + {warnings, + [unmatched_returns, + error_handling, + race_conditions, + underspecs + ]} + ]}. -{xref_checks, [ - undefined_function_calls, - undefined_functions, - locals_not_used, - exports_not_used, - deprecated_function_calls, - deprecated_functions - ]}. - -{eunit_opts, [verbose, {skip_deps, true}]}. -{eunit_exclude_deps, true}. {cover_enabled, true}. + +{profiles, + [{generate_documentation, + [{deps, + [{edown, ".*", {git, "https://github.com/uwiger/edown.git", {tag, "0.8.1"}}}]}, + {edoc_opts, [{doclet, edown_doclet}]} + ]}, + {publish, + [{plugins, [rebar3_hex]}]} + ] +}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/scripts/hackish_inject_version_in_docs.sh b/scripts/hackish_inject_version_in_docs.sh new file mode 100755 index 0000000..b36b7dc --- /dev/null +++ b/scripts/hackish_inject_version_in_docs.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -ex + +VERSION=$(./scripts/version_from_git.sh) +perl -pi -e "s/^\@version .+$/\@version ${VERSION}/g" overview.edoc diff --git a/scripts/hackish_make_docs.sh b/scripts/hackish_make_docs.sh index bb31ed0..507c0b4 100755 --- a/scripts/hackish_make_docs.sh +++ b/scripts/hackish_make_docs.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash # sigh..... -sed -i -e 's/^\(\s*\)%{edown/\1{edown/g' -e 's/^\(\s*\)%{doclet, edown/\1{doclet, edown/g' rebar.config -rebar get-deps -rebar compile -pushd doc -erl -pa ../deps/*/ebin -pz ../ebin -noshell -run edoc_run packages '[""]' '[{source_path, ["../src"]}]' -erl -pa ../deps/*/ebin -pz ../ebin -noshell -run edoc_run packages '[""]' '[{source_path, ["../src"]}, {doclet, edown_doclet}, {top_level_readme, {"../README.md", "https://github.com/g-andrade/nlocks", "master"}}]' -popd -sed -i -e 's/^\(\s*\){edown/\1%{edown/g' -e 's/^\(\s*\){doclet, edown/\1%{doclet, edown/g' rebar.config +rebar3 as generate_documentation compile +mkdir -p _build/generate_documentation/lib/nlocks/doc/ +cp -p overview.edoc _build/generate_documentation/lib/nlocks/doc/ +erl -pa _build/generate_documentation/lib/*/ebin -noshell -run edoc_run application "nlocks" +erl -pa _build/generate_documentation/lib/*/ebin -noshell -run edoc_run application "nlocks" '[{doclet, edown_doclet}, {top_level_readme, {"README.md", "https://github.com/g-andrade/nlocks", "master"}}]' +rm -rf doc +mv _build/generate_documentation/lib/nlocks/doc ./ sed -i -e 's/^\(---------\)$/\n\1/g' README.md diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..d8dc22d --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1 @@ +parse diff --git a/scripts/version_from_git.py b/scripts/version_from_git.py new file mode 100755 index 0000000..672381c --- /dev/null +++ b/scripts/version_from_git.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +import parse +import sys + +git_head = parse.parse('ref: {}', sys.argv[1])[0].strip() +tag_description = sys.argv[2].strip() + +head_parts = git_head.split('/') +if len(head_parts) > 2: + if head_parts[-2] in ['release', 'hotfix', 'support']: + print head_parts[-1] + elif len(head_parts) == 3 and head_parts[-1] in ['master']: + tag_description_parts = tag_description.split('-') + (major, minor, fix) = parse.parse('{:d}.{:d}.{:d}', tag_description_parts[0]) + print '%d.%d.%d' % (major, minor, fix) + else: + print tag_description +else: + print tag_description diff --git a/scripts/version_from_git.sh b/scripts/version_from_git.sh new file mode 100755 index 0000000..6ea8a61 --- /dev/null +++ b/scripts/version_from_git.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -ex + +GIT_HEAD=$(cat .git/HEAD) +TAG_DESCRIPTION=$(git describe --tags) +./scripts/version_from_git.py "$GIT_HEAD" "$TAG_DESCRIPTION"