ost 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +158 -0
- data/lib/ost.rb +14 -18
- data/ost.gemspec +1 -1
- data/test/ost_test.rb +52 -47
- metadata +35 -43
- data/README.markdown +0 -111
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
Ost
|
2
|
+
===
|
3
|
+
|
4
|
+
Redis based queues and workers.
|
5
|
+
|
6
|
+
![Ost Cafe, by Arancia Project](http://farm4.static.flickr.com/3255/3161710005_36566b8a9e.jpg)
|
7
|
+
|
8
|
+
Description
|
9
|
+
-----------
|
10
|
+
|
11
|
+
**Ost** makes it easy to enqueue object ids and process them with
|
12
|
+
workers.
|
13
|
+
|
14
|
+
Say you want to process video uploads. In your application you will
|
15
|
+
have something like this:
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
Ost[:videos_to_process].push(@video.id)
|
19
|
+
```
|
20
|
+
|
21
|
+
Then, you will have a worker that will look like this:
|
22
|
+
|
23
|
+
``` ruby
|
24
|
+
require "ost"
|
25
|
+
|
26
|
+
Ost[:videos_to_process].each do |id|
|
27
|
+
# Do something with it!
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Usage
|
32
|
+
-----
|
33
|
+
|
34
|
+
**Ost** connects to Redis automatically with the default options
|
35
|
+
(localhost:6379, database 0).
|
36
|
+
|
37
|
+
You can customize the connection by calling `connect`:
|
38
|
+
|
39
|
+
``` ruby
|
40
|
+
Ost.connect port: 6380, db: 2
|
41
|
+
```
|
42
|
+
|
43
|
+
Then you only need to refer to a queue for it to pop into existence:
|
44
|
+
|
45
|
+
``` ruby
|
46
|
+
Ost[:rss_feeds] << @feed.id
|
47
|
+
```
|
48
|
+
|
49
|
+
A worker is a Ruby file with this basic code:
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
require "ost"
|
53
|
+
|
54
|
+
Ost[:rss_feeds].each do |id|
|
55
|
+
# ...
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
It will pop items from the queue as soon as they become available. It
|
60
|
+
uses BRPOPLPUSH with a timeout that can be specified with the
|
61
|
+
`OST_TIMEOUT` environment variable.
|
62
|
+
|
63
|
+
Note that in these examples we are pushing numbers to the queue. As
|
64
|
+
we have unlimited queues, each queue should be specialized and the
|
65
|
+
workers must be smart enough to know what to do with the numbers they
|
66
|
+
pop.
|
67
|
+
|
68
|
+
Available methods
|
69
|
+
=================
|
70
|
+
|
71
|
+
`Ost.connect`: configure the connection to Redis. By default, it
|
72
|
+
connects to localhost in port 6379 and uses the database 0. It accepts
|
73
|
+
the same options as [redis-rb](https://github.com/redis/redis-rb).
|
74
|
+
|
75
|
+
`Ost.stop`: halt processing for all queues.
|
76
|
+
|
77
|
+
`Ost[:example].push item`, `Ost[:some_queue] << item`: add `item` to
|
78
|
+
the `:example` queue.
|
79
|
+
|
80
|
+
`Ost[:example].push { |item| ... }`, `Ost[:example].each { |item| ...
|
81
|
+
}`: consume `item` from the `:example` queue. If the block doesn't
|
82
|
+
complete successfully, the item will be left at a backup queue.
|
83
|
+
|
84
|
+
`Ost[:example].stop`: halt processing for the `example` queue.
|
85
|
+
|
86
|
+
Failures
|
87
|
+
========
|
88
|
+
|
89
|
+
**Ost** stores in-process items in backup queues. That allows the
|
90
|
+
developer to deal with exceptions in a way that results adequate
|
91
|
+
for his application.
|
92
|
+
|
93
|
+
There is one backup queue for each worker, with the following
|
94
|
+
convention for naming the key in Redis: given a worker using the
|
95
|
+
`:events` queue, running in the hostname `domU-12-31-39-04-49-C7`
|
96
|
+
with the process id `28431`, the key for the backup queue will be
|
97
|
+
`ost:events:domU-12-31-39-04-49-C7:28431`.
|
98
|
+
|
99
|
+
Here's the explanation for each part:
|
100
|
+
|
101
|
+
* `ost`: namespace for all **Ost** related keys.
|
102
|
+
* `events`: name of the queue.
|
103
|
+
* `domU-12-31-39-04-49-C7`: hostname of the worker.
|
104
|
+
* `28431`: process id of the worker.
|
105
|
+
|
106
|
+
Priorities
|
107
|
+
----------
|
108
|
+
|
109
|
+
There's no concept of priorities, as each queue is specialized and you
|
110
|
+
can create as many as you want. For example, nothing prevents the
|
111
|
+
creation of the `:example_high_priority` or the
|
112
|
+
`:example_low_priority` queues.
|
113
|
+
|
114
|
+
Differences with Delayed::Job and Resque
|
115
|
+
----------------------------------------
|
116
|
+
|
117
|
+
Both [Delayed::Job](http://github.com/tobi/delayed_job) and
|
118
|
+
[Resque](http://github.com/defunkt/resque) provide queues and workers
|
119
|
+
(the latter using Redis). They provide dumb workers that process jobs,
|
120
|
+
which are specialized for each task. The specialization takes place
|
121
|
+
in the application side, and the job is serialized and pushed into a
|
122
|
+
queue.
|
123
|
+
|
124
|
+
**Ost**, by contrast, just pushes numbers into specialized queues, and
|
125
|
+
uses workers that are subscribed to specific queues and know what to
|
126
|
+
do with the items they get. The total sum of logic is about the same,
|
127
|
+
but there's less communication and less data transfer with **Ost**.
|
128
|
+
|
129
|
+
Installation
|
130
|
+
------------
|
131
|
+
|
132
|
+
$ gem install ost
|
133
|
+
|
134
|
+
License
|
135
|
+
-------
|
136
|
+
|
137
|
+
Copyright (c) 2010, 2011, 2012 Michel Martens
|
138
|
+
|
139
|
+
Permission is hereby granted, free of charge, to any person
|
140
|
+
obtaining a copy of this software and associated documentation
|
141
|
+
files (the "Software"), to deal in the Software without
|
142
|
+
restriction, including without limitation the rights to use,
|
143
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
144
|
+
copies of the Software, and to permit persons to whom the
|
145
|
+
Software is furnished to do so, subject to the following
|
146
|
+
conditions:
|
147
|
+
|
148
|
+
The above copyright notice and this permission notice shall be
|
149
|
+
included in all copies or substantial portions of the Software.
|
150
|
+
|
151
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
152
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
153
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
154
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
155
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
156
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
157
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
158
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/ost.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
require "nest"
|
2
2
|
|
3
3
|
module Ost
|
4
|
-
VERSION = "0.0
|
5
|
-
TIMEOUT = ENV["OST_TIMEOUT"] ||
|
4
|
+
VERSION = "0.1.0"
|
5
|
+
TIMEOUT = ENV["OST_TIMEOUT"] || 0
|
6
6
|
|
7
7
|
class Queue
|
8
|
-
attr :
|
8
|
+
attr :key
|
9
|
+
attr :backup
|
9
10
|
|
10
11
|
def initialize(name)
|
11
|
-
@
|
12
|
+
@key = Nest.new(:ost)[name]
|
13
|
+
@backup = @key[Socket.gethostname][Process.pid]
|
12
14
|
end
|
13
15
|
|
14
16
|
def push(value)
|
15
|
-
|
17
|
+
key.lpush(value)
|
16
18
|
end
|
17
19
|
|
18
20
|
def each(&block)
|
@@ -21,28 +23,22 @@ module Ost
|
|
21
23
|
loop do
|
22
24
|
break if @stopping
|
23
25
|
|
24
|
-
|
25
|
-
next if item.nil? or item.empty?
|
26
|
+
item = @key.brpoplpush(@backup, TIMEOUT)
|
26
27
|
|
27
|
-
|
28
|
-
block.call(item)
|
29
|
-
rescue Exception => e
|
30
|
-
error = "#{Time.now} #{ns[item]} => #{e.inspect}"
|
28
|
+
block.call(item)
|
31
29
|
|
32
|
-
|
33
|
-
redis.publish ns[:errors], error
|
34
|
-
end
|
30
|
+
@backup.del
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
|
-
def errors
|
39
|
-
redis.lrange ns[:errors], 0, -1
|
40
|
-
end
|
41
|
-
|
42
34
|
def stop
|
43
35
|
@stopping = true
|
44
36
|
end
|
45
37
|
|
38
|
+
def items
|
39
|
+
key.lrange(0, -1)
|
40
|
+
end
|
41
|
+
|
46
42
|
alias << push
|
47
43
|
alias pop each
|
48
44
|
|
data/ost.gemspec
CHANGED
data/test/ost_test.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
ENV["OST_TIMEOUT"] = "1"
|
2
|
+
|
1
3
|
require File.expand_path("test_helper", File.dirname(__FILE__))
|
2
4
|
|
3
5
|
scope do
|
4
6
|
def ost(&job)
|
5
7
|
thread = Thread.new do
|
6
|
-
Ost[:events].each
|
8
|
+
Ost[:events].each do |item|
|
9
|
+
begin
|
10
|
+
yield(item)
|
11
|
+
ensure
|
12
|
+
thread.kill
|
13
|
+
end
|
14
|
+
end
|
7
15
|
end
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
thread.kill
|
17
|
+
thread.join
|
12
18
|
end
|
13
19
|
|
14
20
|
def enqueue(id)
|
@@ -24,9 +30,14 @@ scope do
|
|
24
30
|
Redis.new
|
25
31
|
end
|
26
32
|
|
27
|
-
test "
|
33
|
+
test "allows access to the queued items" do
|
28
34
|
enqueue(1)
|
29
|
-
|
35
|
+
|
36
|
+
assert_equal ["1"], Ost[:events].items
|
37
|
+
end
|
38
|
+
|
39
|
+
test "allows access to the underlying key" do
|
40
|
+
assert_equal 0, Ost[:events].key.llen
|
30
41
|
end
|
31
42
|
|
32
43
|
test "process items from the queue" do |redis|
|
@@ -38,50 +49,10 @@ scope do
|
|
38
49
|
results << item
|
39
50
|
end
|
40
51
|
|
41
|
-
assert_equal [],
|
52
|
+
assert_equal [], Ost[:events].items
|
42
53
|
assert_equal ["1"], results
|
43
54
|
end
|
44
55
|
|
45
|
-
test "add failures to special lists" do |redis|
|
46
|
-
enqueue(1)
|
47
|
-
|
48
|
-
ost do |item|
|
49
|
-
raise "Wrong answer"
|
50
|
-
end
|
51
|
-
|
52
|
-
assert_equal 0, redis.llen("ost:events")
|
53
|
-
assert_equal 1, redis.llen("ost:events:errors")
|
54
|
-
|
55
|
-
assert redis.rpop("ost:events:errors").match(/ost:events:1 => #<RuntimeError: Wrong answer/)
|
56
|
-
end
|
57
|
-
|
58
|
-
test "publish the error to a specific channel" do |redis|
|
59
|
-
enqueue(1)
|
60
|
-
results = []
|
61
|
-
|
62
|
-
t1 = Thread.new do
|
63
|
-
redis.subscribe("ost:events:errors") do |on|
|
64
|
-
on.message do |channel, message|
|
65
|
-
if message[/ost:events:1 => #<RuntimeError: Wrong answer/]
|
66
|
-
results << message
|
67
|
-
redis.unsubscribe
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
ost do |item|
|
74
|
-
raise "Wrong answer"
|
75
|
-
end
|
76
|
-
|
77
|
-
t1.join
|
78
|
-
|
79
|
-
assert_equal 0, redis.llen("ost:events")
|
80
|
-
assert_equal 1, redis.llen("ost:events:errors")
|
81
|
-
|
82
|
-
assert results.pop.match(/ost:events:1 => #<RuntimeError: Wrong answer/)
|
83
|
-
end
|
84
|
-
|
85
56
|
test "halt processing a queue" do
|
86
57
|
Thread.new do
|
87
58
|
sleep 0.5
|
@@ -107,4 +78,38 @@ scope do
|
|
107
78
|
|
108
79
|
assert true
|
109
80
|
end
|
81
|
+
|
82
|
+
test "maintains a backup queue for when worker dies" do
|
83
|
+
enqueue(1)
|
84
|
+
|
85
|
+
assert_equal 0, Ost[:events].backup.llen
|
86
|
+
|
87
|
+
begin
|
88
|
+
Ost[:events].each do |item|
|
89
|
+
item.some_error
|
90
|
+
end
|
91
|
+
rescue
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_equal ["1"], Ost[:events].backup.lrange(0, -1)
|
95
|
+
end
|
96
|
+
|
97
|
+
test "cleans up the backup queue on success" do
|
98
|
+
enqueue(1)
|
99
|
+
|
100
|
+
done = false
|
101
|
+
|
102
|
+
Thread.new do
|
103
|
+
Ost[:events].each do |item|
|
104
|
+
assert_equal ["1"], Ost[:events].backup.lrange(0, -1)
|
105
|
+
done = true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
until done; end
|
110
|
+
|
111
|
+
Ost[:events].stop
|
112
|
+
|
113
|
+
assert_equal 0, Ost[:events].backup.llen
|
114
|
+
end
|
110
115
|
end
|
metadata
CHANGED
@@ -1,51 +1,47 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: ost
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.0.3
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Michel Martens
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-03-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
16
15
|
name: nest
|
17
|
-
|
18
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2160446460 !ruby/object:Gem::Requirement
|
19
17
|
none: false
|
20
|
-
requirements:
|
18
|
+
requirements:
|
21
19
|
- - ~>
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version:
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
24
22
|
type: :runtime
|
25
|
-
version_requirements: *id001
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name: cutest
|
28
23
|
prerelease: false
|
29
|
-
|
24
|
+
version_requirements: *2160446460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: cutest
|
27
|
+
requirement: &2160445920 !ruby/object:Gem::Requirement
|
30
28
|
none: false
|
31
|
-
requirements:
|
29
|
+
requirements:
|
32
30
|
- - ~>
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version:
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
35
33
|
type: :development
|
36
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2160445920
|
37
36
|
description: Ost lets you manage queues and workers with Redis.
|
38
|
-
email:
|
37
|
+
email:
|
39
38
|
- michel@soveran.com
|
40
39
|
executables: []
|
41
|
-
|
42
40
|
extensions: []
|
43
|
-
|
44
41
|
extra_rdoc_files: []
|
45
|
-
|
46
|
-
files:
|
42
|
+
files:
|
47
43
|
- LICENSE
|
48
|
-
- README.
|
44
|
+
- README.md
|
49
45
|
- Rakefile
|
50
46
|
- lib/ost.rb
|
51
47
|
- ost.gemspec
|
@@ -53,30 +49,26 @@ files:
|
|
53
49
|
- test/test_helper.rb
|
54
50
|
homepage: http://github.com/soveran/ost
|
55
51
|
licenses: []
|
56
|
-
|
57
52
|
post_install_message:
|
58
53
|
rdoc_options: []
|
59
|
-
|
60
|
-
require_paths:
|
54
|
+
require_paths:
|
61
55
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
57
|
none: false
|
64
|
-
requirements:
|
65
|
-
- -
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version:
|
68
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
63
|
none: false
|
70
|
-
requirements:
|
71
|
-
- -
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version:
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
74
68
|
requirements: []
|
75
|
-
|
76
69
|
rubyforge_project:
|
77
|
-
rubygems_version: 1.8.
|
70
|
+
rubygems_version: 1.8.10
|
78
71
|
signing_key:
|
79
72
|
specification_version: 3
|
80
73
|
summary: Redis based queues and workers.
|
81
74
|
test_files: []
|
82
|
-
|
data/README.markdown
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
Ost
|
2
|
-
===
|
3
|
-
|
4
|
-
Redis based queues and workers.
|
5
|
-
|
6
|
-
![Ost Cafe, by Arancia Project](http://farm4.static.flickr.com/3255/3161710005_36566b8a9e.jpg)
|
7
|
-
|
8
|
-
Description
|
9
|
-
-----------
|
10
|
-
|
11
|
-
**Ost** makes it easy to enqueue object ids and process them with workers.
|
12
|
-
|
13
|
-
Say you want to process video uploads. In your application you will have something like this:
|
14
|
-
|
15
|
-
Ost[:videos_to_process].push(@video.id)
|
16
|
-
|
17
|
-
Then, you will have a worker that will look like this:
|
18
|
-
|
19
|
-
require "ost"
|
20
|
-
|
21
|
-
Ost[:videos_to_process].each do |id|
|
22
|
-
# Do something with it!
|
23
|
-
end
|
24
|
-
|
25
|
-
Usage
|
26
|
-
-----
|
27
|
-
|
28
|
-
**Ost** connects to Redis automatically with the default options (localhost:6379, database 0).
|
29
|
-
|
30
|
-
You can customize the connection by calling `connect`:
|
31
|
-
|
32
|
-
Ost.connect port: 6380, db: 2
|
33
|
-
|
34
|
-
Then you only need to refer to a queue for it to pop into existence:
|
35
|
-
|
36
|
-
Ost[:rss_feeds] << @feed.id
|
37
|
-
|
38
|
-
A worker is a Ruby file with this basic code:
|
39
|
-
|
40
|
-
require "ost"
|
41
|
-
|
42
|
-
Ost[:rss_feeds].each do |id|
|
43
|
-
...
|
44
|
-
end
|
45
|
-
|
46
|
-
It will pop items from the queue with a timeout of two seconds and retry indefinitely. If you want to configure the timeout, set the environment variable `OST_TIMEOUT`.
|
47
|
-
|
48
|
-
Available methods for a queue are `push` (aliased to `<<`) and `pop` (aliased to `each`).
|
49
|
-
|
50
|
-
Note that in these examples we are pushing numbers to the queue. As we have unlimited queues, each queue should be specialized and the workers must be smart enough to know what to do with the numbers they pop.
|
51
|
-
|
52
|
-
Failures
|
53
|
-
========
|
54
|
-
|
55
|
-
If the block raises an error, it is captured by **Ost** and the exception is logged in Redis.
|
56
|
-
|
57
|
-
Consider this example:
|
58
|
-
|
59
|
-
require "ost"
|
60
|
-
|
61
|
-
Ost[:rss_feeds].each do |id|
|
62
|
-
...
|
63
|
-
raise "Invalid format"
|
64
|
-
end
|
65
|
-
|
66
|
-
Then, in the console you can do:
|
67
|
-
|
68
|
-
>> Ost[:rss_feeds].push 1
|
69
|
-
=> 1
|
70
|
-
|
71
|
-
>> Ost[:rss_feeds].errors
|
72
|
-
=> ["2010-04-12 21:57:23 -0300 ost:rss_feeds:1 => #<RuntimeError: Invalid format>"]
|
73
|
-
|
74
|
-
Differences with Delayed::Job and Resque
|
75
|
-
--------------------------------------
|
76
|
-
|
77
|
-
Both [Delayed::Job](http://github.com/tobi/delayed_job) and [Resque](http://github.com/defunkt/resque)
|
78
|
-
provide queues and workers (the latter using Redis). They provide dumb workers that process jobs, which are specialized for each task. The specialization takes place in the application side, and the job is serialized and pushed into a queue.
|
79
|
-
|
80
|
-
**Ost**, by contrast, just pushes numbers into specialized queues, and uses workers that are subscribed to specific queues and know what to do with the items they get. The total sum of logic is almost the same.
|
81
|
-
|
82
|
-
Installation
|
83
|
-
------------
|
84
|
-
|
85
|
-
$ gem install ost
|
86
|
-
|
87
|
-
License
|
88
|
-
-------
|
89
|
-
|
90
|
-
Copyright (c) 2010 Michel Martens
|
91
|
-
|
92
|
-
Permission is hereby granted, free of charge, to any person
|
93
|
-
obtaining a copy of this software and associated documentation
|
94
|
-
files (the "Software"), to deal in the Software without
|
95
|
-
restriction, including without limitation the rights to use,
|
96
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
97
|
-
copies of the Software, and to permit persons to whom the
|
98
|
-
Software is furnished to do so, subject to the following
|
99
|
-
conditions:
|
100
|
-
|
101
|
-
The above copyright notice and this permission notice shall be
|
102
|
-
included in all copies or substantial portions of the Software.
|
103
|
-
|
104
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
105
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
106
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
107
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
108
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
109
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
110
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
111
|
-
OTHER DEALINGS IN THE SOFTWARE.
|