Redis lock implementation The 2019 Stack Overflow Developer Survey Results Are InRead-write...
Can I have a signal generator on while it's not connected?
Why are there uneven bright areas in this photo of black hole?
Why can't devices on different VLANs, but on the same subnet, communicate?
What could be the right powersource for 15 seconds lifespan disposable giant chainsaw?
"as much details as you can remember"
Falsification in Math vs Science
Inverse Relationship Between Precision and Recall
How do PCB vias affect signal quality?
How to charge AirPods to keep battery healthy?
Why was M87 targeted for the Event Horizon Telescope instead of Sagittarius A*?
How come people say “Would of”?
What to do when moving next to a bird sanctuary with a loosely-domesticated cat?
Pokemon Turn Based battle (Python)
What do these terms in Caesar's Gallic Wars mean?
Likelihood that a superbug or lethal virus could come from a landfill
Is an up-to-date browser secure on an out-of-date OS?
Why “相同意思的词” is called “同义词” instead of "同意词"?
What is the meaning of Triage in Cybersec world?
Is it okay to consider publishing in my first year of PhD?
What is the motivation for a law requiring 2 parties to consent for recording a conversation
How can I define good in a religion that claims no moral authority?
How to display lines in a file like ls displays files in a directory?
Star Trek - X-shaped Item on Regula/Orbital Office Starbases
How to type a long/em dash `—`
Redis lock implementation
The 2019 Stack Overflow Developer Survey Results Are InRead-write lock implementation for Ruby, new versionChat logger using RedisSpin-lock implementationRedis Message Queue (Cloud)Token generating library on RedisSilly redis implementationRedis protocol implementation by KotlinRedis lookup with a default valueRedis lua - game leaderboard “overview”Reader Writer Lock Implementation
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
$begingroup$
Considering I'm not implementing distributed lock mechanism, is this code correct and clear?
class RedisLock
class NotAcquired < StandardError; end
def initialize(redis)
@redis = redis
end
def lock(key, expiration_ms)
val = SecureRandom.random_number(100000000000000000).to_s
if @redis.set(key, val, nx: true, px: expiration_ms)
yield
unlock(key, val)
true
else
false
end
end
def lock!(*args, &block)
unless lock(*args, &block)
raise NotAcquired.new("Could not acquire lock with #{args}")
end
end
def unlock(key, val)
check_and_delete = <<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
@redis.eval(check_and_delete, [key, val])
end
end
RedisLock.new(Redis.current).lock!('key', 10000) { do_something }
ruby locking redis
$endgroup$
bumped to the homepage by Community♦ 9 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
$begingroup$
Considering I'm not implementing distributed lock mechanism, is this code correct and clear?
class RedisLock
class NotAcquired < StandardError; end
def initialize(redis)
@redis = redis
end
def lock(key, expiration_ms)
val = SecureRandom.random_number(100000000000000000).to_s
if @redis.set(key, val, nx: true, px: expiration_ms)
yield
unlock(key, val)
true
else
false
end
end
def lock!(*args, &block)
unless lock(*args, &block)
raise NotAcquired.new("Could not acquire lock with #{args}")
end
end
def unlock(key, val)
check_and_delete = <<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
@redis.eval(check_and_delete, [key, val])
end
end
RedisLock.new(Redis.current).lock!('key', 10000) { do_something }
ruby locking redis
$endgroup$
bumped to the homepage by Community♦ 9 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
$begingroup$
Considering I'm not implementing distributed lock mechanism, is this code correct and clear?
class RedisLock
class NotAcquired < StandardError; end
def initialize(redis)
@redis = redis
end
def lock(key, expiration_ms)
val = SecureRandom.random_number(100000000000000000).to_s
if @redis.set(key, val, nx: true, px: expiration_ms)
yield
unlock(key, val)
true
else
false
end
end
def lock!(*args, &block)
unless lock(*args, &block)
raise NotAcquired.new("Could not acquire lock with #{args}")
end
end
def unlock(key, val)
check_and_delete = <<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
@redis.eval(check_and_delete, [key, val])
end
end
RedisLock.new(Redis.current).lock!('key', 10000) { do_something }
ruby locking redis
$endgroup$
Considering I'm not implementing distributed lock mechanism, is this code correct and clear?
class RedisLock
class NotAcquired < StandardError; end
def initialize(redis)
@redis = redis
end
def lock(key, expiration_ms)
val = SecureRandom.random_number(100000000000000000).to_s
if @redis.set(key, val, nx: true, px: expiration_ms)
yield
unlock(key, val)
true
else
false
end
end
def lock!(*args, &block)
unless lock(*args, &block)
raise NotAcquired.new("Could not acquire lock with #{args}")
end
end
def unlock(key, val)
check_and_delete = <<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
@redis.eval(check_and_delete, [key, val])
end
end
RedisLock.new(Redis.current).lock!('key', 10000) { do_something }
ruby locking redis
ruby locking redis
edited Mar 22 '17 at 12:42
200_success
131k17157422
131k17157422
asked Feb 9 '17 at 18:26
cutalioncutalion
1645
1645
bumped to the homepage by Community♦ 9 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
bumped to the homepage by Community♦ 9 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
Here's a stab at clarifying the intent without changing any of the functionality. I didn't try to run it or anything, so don't hold me to syntax.
# handles locking and unlocking redis for a particular operation
class RedisLock
# thrown when a lock cannot be acquired
class NotAcquired < StandardError; end
# since the redis key and ms are used in pretty much every method,
# might as well go ahead and pass them in the constructor.
# set defaults here if there are defaults that make sense.
# make a yardoc comment and describe what these all are.
def initialize(redis: Redis.current, nx: true, redis_key:, expiration_ms:)
@redis = redis
@redis_key = redis_key
@expiration_ms = expiration_ms
@nx = nx
end
# call it with_lock instead of lock
# to make it more apparent it accepts a block
def with_lock!
raise(NotAcquired, "Could not acquire lock with #{args}") unless lock
yield
unlock(redis_key, random_lock_number)
true # do you really need to return true here?
end
def with_lock
with_lock!
rescue NotAcquired
false
end
private
# set instance variables in the initializer, but never call them directly.
attr_reader :redis, :redis_key, :expiration_ms, :nx
def lock
redis.set(redis_key, random_lock_number, nx: nx, px: expiration_ms)
end
# number is a little decieving here when you call `.to_s` at the end.
# can you use SecureRandom.uuid instead?
def random_lock_number
@random_lock_number ||= SecureRandom.random_number(100_000_000_000_000_000).to_s
end
# no need for this to be exposed publicly.
# calling eval directly is usually a bad idea.
def unlock
redis.eval(redis_check_and_delete, [redis_key, random_lock_number])
end
def redis_check_and_delete
<<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
end
end
And to call it:
RedisLock.new(redis_key: 'key', expiration_ms: 10000).with_lock! { do_something }
$endgroup$
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (likenx
in parameters makes no sense), but this is not so important after the first point.
$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the classRedisLock
and not something likeRedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the!
version of a method is called by the non-bang version instead of the other way around. good luck.
$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f154936%2fredis-lock-implementation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Here's a stab at clarifying the intent without changing any of the functionality. I didn't try to run it or anything, so don't hold me to syntax.
# handles locking and unlocking redis for a particular operation
class RedisLock
# thrown when a lock cannot be acquired
class NotAcquired < StandardError; end
# since the redis key and ms are used in pretty much every method,
# might as well go ahead and pass them in the constructor.
# set defaults here if there are defaults that make sense.
# make a yardoc comment and describe what these all are.
def initialize(redis: Redis.current, nx: true, redis_key:, expiration_ms:)
@redis = redis
@redis_key = redis_key
@expiration_ms = expiration_ms
@nx = nx
end
# call it with_lock instead of lock
# to make it more apparent it accepts a block
def with_lock!
raise(NotAcquired, "Could not acquire lock with #{args}") unless lock
yield
unlock(redis_key, random_lock_number)
true # do you really need to return true here?
end
def with_lock
with_lock!
rescue NotAcquired
false
end
private
# set instance variables in the initializer, but never call them directly.
attr_reader :redis, :redis_key, :expiration_ms, :nx
def lock
redis.set(redis_key, random_lock_number, nx: nx, px: expiration_ms)
end
# number is a little decieving here when you call `.to_s` at the end.
# can you use SecureRandom.uuid instead?
def random_lock_number
@random_lock_number ||= SecureRandom.random_number(100_000_000_000_000_000).to_s
end
# no need for this to be exposed publicly.
# calling eval directly is usually a bad idea.
def unlock
redis.eval(redis_check_and_delete, [redis_key, random_lock_number])
end
def redis_check_and_delete
<<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
end
end
And to call it:
RedisLock.new(redis_key: 'key', expiration_ms: 10000).with_lock! { do_something }
$endgroup$
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (likenx
in parameters makes no sense), but this is not so important after the first point.
$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the classRedisLock
and not something likeRedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the!
version of a method is called by the non-bang version instead of the other way around. good luck.
$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
add a comment |
$begingroup$
Here's a stab at clarifying the intent without changing any of the functionality. I didn't try to run it or anything, so don't hold me to syntax.
# handles locking and unlocking redis for a particular operation
class RedisLock
# thrown when a lock cannot be acquired
class NotAcquired < StandardError; end
# since the redis key and ms are used in pretty much every method,
# might as well go ahead and pass them in the constructor.
# set defaults here if there are defaults that make sense.
# make a yardoc comment and describe what these all are.
def initialize(redis: Redis.current, nx: true, redis_key:, expiration_ms:)
@redis = redis
@redis_key = redis_key
@expiration_ms = expiration_ms
@nx = nx
end
# call it with_lock instead of lock
# to make it more apparent it accepts a block
def with_lock!
raise(NotAcquired, "Could not acquire lock with #{args}") unless lock
yield
unlock(redis_key, random_lock_number)
true # do you really need to return true here?
end
def with_lock
with_lock!
rescue NotAcquired
false
end
private
# set instance variables in the initializer, but never call them directly.
attr_reader :redis, :redis_key, :expiration_ms, :nx
def lock
redis.set(redis_key, random_lock_number, nx: nx, px: expiration_ms)
end
# number is a little decieving here when you call `.to_s` at the end.
# can you use SecureRandom.uuid instead?
def random_lock_number
@random_lock_number ||= SecureRandom.random_number(100_000_000_000_000_000).to_s
end
# no need for this to be exposed publicly.
# calling eval directly is usually a bad idea.
def unlock
redis.eval(redis_check_and_delete, [redis_key, random_lock_number])
end
def redis_check_and_delete
<<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
end
end
And to call it:
RedisLock.new(redis_key: 'key', expiration_ms: 10000).with_lock! { do_something }
$endgroup$
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (likenx
in parameters makes no sense), but this is not so important after the first point.
$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the classRedisLock
and not something likeRedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the!
version of a method is called by the non-bang version instead of the other way around. good luck.
$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
add a comment |
$begingroup$
Here's a stab at clarifying the intent without changing any of the functionality. I didn't try to run it or anything, so don't hold me to syntax.
# handles locking and unlocking redis for a particular operation
class RedisLock
# thrown when a lock cannot be acquired
class NotAcquired < StandardError; end
# since the redis key and ms are used in pretty much every method,
# might as well go ahead and pass them in the constructor.
# set defaults here if there are defaults that make sense.
# make a yardoc comment and describe what these all are.
def initialize(redis: Redis.current, nx: true, redis_key:, expiration_ms:)
@redis = redis
@redis_key = redis_key
@expiration_ms = expiration_ms
@nx = nx
end
# call it with_lock instead of lock
# to make it more apparent it accepts a block
def with_lock!
raise(NotAcquired, "Could not acquire lock with #{args}") unless lock
yield
unlock(redis_key, random_lock_number)
true # do you really need to return true here?
end
def with_lock
with_lock!
rescue NotAcquired
false
end
private
# set instance variables in the initializer, but never call them directly.
attr_reader :redis, :redis_key, :expiration_ms, :nx
def lock
redis.set(redis_key, random_lock_number, nx: nx, px: expiration_ms)
end
# number is a little decieving here when you call `.to_s` at the end.
# can you use SecureRandom.uuid instead?
def random_lock_number
@random_lock_number ||= SecureRandom.random_number(100_000_000_000_000_000).to_s
end
# no need for this to be exposed publicly.
# calling eval directly is usually a bad idea.
def unlock
redis.eval(redis_check_and_delete, [redis_key, random_lock_number])
end
def redis_check_and_delete
<<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
end
end
And to call it:
RedisLock.new(redis_key: 'key', expiration_ms: 10000).with_lock! { do_something }
$endgroup$
Here's a stab at clarifying the intent without changing any of the functionality. I didn't try to run it or anything, so don't hold me to syntax.
# handles locking and unlocking redis for a particular operation
class RedisLock
# thrown when a lock cannot be acquired
class NotAcquired < StandardError; end
# since the redis key and ms are used in pretty much every method,
# might as well go ahead and pass them in the constructor.
# set defaults here if there are defaults that make sense.
# make a yardoc comment and describe what these all are.
def initialize(redis: Redis.current, nx: true, redis_key:, expiration_ms:)
@redis = redis
@redis_key = redis_key
@expiration_ms = expiration_ms
@nx = nx
end
# call it with_lock instead of lock
# to make it more apparent it accepts a block
def with_lock!
raise(NotAcquired, "Could not acquire lock with #{args}") unless lock
yield
unlock(redis_key, random_lock_number)
true # do you really need to return true here?
end
def with_lock
with_lock!
rescue NotAcquired
false
end
private
# set instance variables in the initializer, but never call them directly.
attr_reader :redis, :redis_key, :expiration_ms, :nx
def lock
redis.set(redis_key, random_lock_number, nx: nx, px: expiration_ms)
end
# number is a little decieving here when you call `.to_s` at the end.
# can you use SecureRandom.uuid instead?
def random_lock_number
@random_lock_number ||= SecureRandom.random_number(100_000_000_000_000_000).to_s
end
# no need for this to be exposed publicly.
# calling eval directly is usually a bad idea.
def unlock
redis.eval(redis_check_and_delete, [redis_key, random_lock_number])
end
def redis_check_and_delete
<<-LUA
if redis.call('get', KEYS[1]) == KEYS[2] then
redis.call('del', KEYS[1])
end
LUA
end
end
And to call it:
RedisLock.new(redis_key: 'key', expiration_ms: 10000).with_lock! { do_something }
edited Feb 20 '17 at 7:55
answered Feb 20 '17 at 7:50
Chris DrappierChris Drappier
255129
255129
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (likenx
in parameters makes no sense), but this is not so important after the first point.
$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the classRedisLock
and not something likeRedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the!
version of a method is called by the non-bang version instead of the other way around. good luck.
$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
add a comment |
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (likenx
in parameters makes no sense), but this is not so important after the first point.
$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the classRedisLock
and not something likeRedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the!
version of a method is called by the non-bang version instead of the other way around. good luck.
$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (like
nx
in parameters makes no sense), but this is not so important after the first point.$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
Hi @Chris! Thanks you for the answer! Though I cannot agree with the suggested improvements. The general concern about it is that the new object cannot be re-used with another key. Moreover it even cannot be re-used with the same key. I also have minor concerns (like
nx
in parameters makes no sense), but this is not so important after the first point.$endgroup$
– cutalion
Feb 20 '17 at 12:56
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the class
RedisLock
and not something like RedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the !
version of a method is called by the non-bang version instead of the other way around. good luck.$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
$begingroup$
object instantiation is free. You have no need to to re-use the single object. the Redis connection is already passed in. you've even named the class
RedisLock
and not something like RedisLockFactory
. The major takeaway here, if you care to take away anything is that your methods are doing too much internally. If you're passing arguments into every single method, that's a code smell. also usually the !
version of a method is called by the non-bang version instead of the other way around. good luck.$endgroup$
– Chris Drappier
Feb 20 '17 at 16:42
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f154936%2fredis-lock-implementation%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown