thinner 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README +17 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bin/thinner +4 -0
- data/doc/Thinner/Client.html +602 -0
- data/doc/Thinner/CommandLine.html +291 -0
- data/doc/Thinner/Configuration.html +592 -0
- data/doc/Thinner/Purger.html +433 -0
- data/doc/Thinner.html +455 -0
- data/doc/_index.html +138 -0
- data/doc/class_list.html +36 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +53 -0
- data/doc/css/style.css +307 -0
- data/doc/file.README.html +71 -0
- data/doc/file_list.html +38 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +71 -0
- data/doc/js/app.js +202 -0
- data/doc/js/full_list.js +149 -0
- data/doc/js/jquery.js +154 -0
- data/doc/method_list.html +211 -0
- data/doc/top-level-namespace.html +88 -0
- data/documentation/css/dawn.css +121 -0
- data/documentation/css/styles.css +53 -0
- data/documentation/examples/configure.rb +25 -0
- data/documentation/examples/purge.rb +5 -0
- data/documentation/images/proplogo.png +0 -0
- data/documentation/index.html.erb +64 -0
- data/index.html +87 -0
- data/lib/thinner/client.rb +88 -0
- data/lib/thinner/command_line.rb +80 -0
- data/lib/thinner/configuration.rb +37 -0
- data/lib/thinner/purger.rb +46 -0
- data/lib/thinner.rb +37 -0
- data/test/helper.rb +10 -0
- data/test/test_thinner.rb +32 -0
- data/thinner.gemspec +90 -0
- metadata +138 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
pre.dawn .MetaSeparator {
|
2
|
+
font-weight: bold;
|
3
|
+
background-color: #DCDCDC;
|
4
|
+
color: #19356D;
|
5
|
+
}
|
6
|
+
pre.dawn .SupportVariable {
|
7
|
+
color: #234A97;
|
8
|
+
}
|
9
|
+
pre.dawn .Constant {
|
10
|
+
font-weight: bold;
|
11
|
+
color: #811F24;
|
12
|
+
}
|
13
|
+
pre.dawn .EmbeddedSource {
|
14
|
+
background-color: #829AC2;
|
15
|
+
}
|
16
|
+
pre.dawn .StringRegexpConstantCharacterEscape {
|
17
|
+
font-weight: bold;
|
18
|
+
color: #811F24;
|
19
|
+
}
|
20
|
+
pre.dawn .Support {
|
21
|
+
color: #691C97;
|
22
|
+
}
|
23
|
+
pre.dawn .MarkupList {
|
24
|
+
color: #693A17;
|
25
|
+
}
|
26
|
+
pre.dawn .Storage {
|
27
|
+
color: #A71D5D;
|
28
|
+
font-style: italic;
|
29
|
+
}
|
30
|
+
pre.dawn .line-numbers {
|
31
|
+
background-color: #7496CF;
|
32
|
+
color: #000000;
|
33
|
+
}
|
34
|
+
pre.dawn .StringConstant {
|
35
|
+
font-weight: bold;
|
36
|
+
color: #696969;
|
37
|
+
}
|
38
|
+
pre.dawn .MarkupUnderline {
|
39
|
+
text-decoration: underline;
|
40
|
+
color: #080808;
|
41
|
+
}
|
42
|
+
pre.dawn .MarkupHeading {
|
43
|
+
font-weight: bold;
|
44
|
+
color: #19356D;
|
45
|
+
}
|
46
|
+
pre.dawn .SupportConstant {
|
47
|
+
color: #B4371F;
|
48
|
+
}
|
49
|
+
pre.dawn .MarkupQuote {
|
50
|
+
background-color: #C5C5C5;
|
51
|
+
color: #0B6125;
|
52
|
+
font-style: italic;
|
53
|
+
}
|
54
|
+
pre.dawn .StringRegexpSpecial {
|
55
|
+
font-weight: bold;
|
56
|
+
color: #CF5628;
|
57
|
+
}
|
58
|
+
pre.dawn .InvalidIllegal {
|
59
|
+
background-color: #B52A1D;
|
60
|
+
color: #F8F8F8;
|
61
|
+
font-style: italic;
|
62
|
+
}
|
63
|
+
pre.dawn .MarkupDeleted {
|
64
|
+
color: #B52A1D;
|
65
|
+
}
|
66
|
+
pre.dawn .MarkupRaw {
|
67
|
+
background-color: #C5C5C5;
|
68
|
+
color: #234A97;
|
69
|
+
}
|
70
|
+
pre.dawn .SupportFunction {
|
71
|
+
color: #693A17;
|
72
|
+
}
|
73
|
+
pre.dawn .PunctuationSeparator {
|
74
|
+
color: #794938;
|
75
|
+
}
|
76
|
+
pre.dawn .StringRegexp {
|
77
|
+
color: #CF5628;
|
78
|
+
}
|
79
|
+
pre.dawn .StringEmbeddedSource {
|
80
|
+
background-color: #829AC2;
|
81
|
+
color: #080808;
|
82
|
+
}
|
83
|
+
pre.dawn .MarkupLink {
|
84
|
+
color: #234A97;
|
85
|
+
font-style: italic;
|
86
|
+
}
|
87
|
+
pre.dawn .MarkupBold {
|
88
|
+
font-weight: bold;
|
89
|
+
color: #080808;
|
90
|
+
}
|
91
|
+
pre.dawn .StringVariable {
|
92
|
+
color: #234A97;
|
93
|
+
}
|
94
|
+
pre.dawn .String {
|
95
|
+
color: #0B6125;
|
96
|
+
}
|
97
|
+
pre.dawn .Keyword {
|
98
|
+
color: #794938;
|
99
|
+
}
|
100
|
+
pre.dawn {
|
101
|
+
background-color: #F5F5F5;
|
102
|
+
color: #080808;
|
103
|
+
}
|
104
|
+
pre.dawn .MarkupItalic {
|
105
|
+
color: #080808;
|
106
|
+
font-style: italic;
|
107
|
+
}
|
108
|
+
pre.dawn .InvalidDeprecated {
|
109
|
+
font-weight: bold;
|
110
|
+
color: #B52A1D;
|
111
|
+
}
|
112
|
+
pre.dawn .Variable {
|
113
|
+
color: #234A97;
|
114
|
+
}
|
115
|
+
pre.dawn .Entity {
|
116
|
+
color: #BF4F24;
|
117
|
+
}
|
118
|
+
pre.dawn .Comment {
|
119
|
+
color: #5A525F;
|
120
|
+
font-style: italic;
|
121
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
body {
|
2
|
+
font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
|
3
|
+
font-size: 16px;
|
4
|
+
line-height:20px;
|
5
|
+
width: 600px;
|
6
|
+
margin-left:auto;
|
7
|
+
margin-right:auto;
|
8
|
+
background: #f4f4f4;
|
9
|
+
}
|
10
|
+
a.propublica{
|
11
|
+
position:absolute;
|
12
|
+
background: transparent url(../images/proplogo.png) no-repeat -40px -20px;
|
13
|
+
top: 0;
|
14
|
+
left: 0;
|
15
|
+
width: 160px;
|
16
|
+
height: 141px;
|
17
|
+
}
|
18
|
+
|
19
|
+
pre {
|
20
|
+
font-family: Monaco, Courier, monospace;
|
21
|
+
font-size: 12px;
|
22
|
+
line-height: 16px;
|
23
|
+
padding:0.5em 1em;
|
24
|
+
overflow: auto;
|
25
|
+
border-left: 4px solid #143D8D;
|
26
|
+
margin-left: 1em;
|
27
|
+
}
|
28
|
+
a {
|
29
|
+
color: #143D8D;
|
30
|
+
text-decoration: none;
|
31
|
+
font-weight: bold;
|
32
|
+
}
|
33
|
+
ul {
|
34
|
+
margin:0 1em;
|
35
|
+
padding:0;
|
36
|
+
list-style: none;
|
37
|
+
}
|
38
|
+
li {
|
39
|
+
margin:0;
|
40
|
+
padding:0;
|
41
|
+
}
|
42
|
+
strong {
|
43
|
+
font-family: Monaco, Courier, monospace;
|
44
|
+
font-weight: normal;
|
45
|
+
background: #dadee5;
|
46
|
+
border: 1px solid #aaa;
|
47
|
+
padding: 1px 2px;
|
48
|
+
font-size: 12px;
|
49
|
+
}
|
50
|
+
p{ margin: 0 0 1em 0 }
|
51
|
+
h3{
|
52
|
+
margin-bottom: 0px;
|
53
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Thinner.configure do |config|
|
2
|
+
# Number of urls to purge at one time. These purge requests are fired in quick
|
3
|
+
# succession. Thinner is perfectly capable of killing a Varnish server, by
|
4
|
+
# overloading the worker thread, so be really conservative with this option.
|
5
|
+
config.batch_length = 10
|
6
|
+
|
7
|
+
# The amount of time to sleep between purges in seconds.
|
8
|
+
config.sleep_time = 1
|
9
|
+
|
10
|
+
# The server address and management port. See:
|
11
|
+
# http://www.varnish-cache.org/trac/wiki/ManagementPort
|
12
|
+
# for details.
|
13
|
+
config.server = "127.0.0.1:6082"
|
14
|
+
|
15
|
+
# By default, every time you call Thinner.purge! thinner spins off a new
|
16
|
+
# instance of Thinner::Client and terminates any old instances that are
|
17
|
+
# running. If you want to have overlapping instances set this to true.
|
18
|
+
# It's not recommended to have multiple Thinner::Client's running at the
|
19
|
+
# same time.
|
20
|
+
config.no_kill = false
|
21
|
+
|
22
|
+
# The log file (either a string or file object) to log the current batch to.
|
23
|
+
# Defaults to STDOUT
|
24
|
+
config.log_file = STDOUT
|
25
|
+
end
|
Binary file
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<%
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__), "/../lib/thinner")
|
3
|
+
DOCS = "documentation/examples/"
|
4
|
+
require 'uv'
|
5
|
+
def code_for(file)
|
6
|
+
return '' unless File.exists?("#{DOCS}#{file}.rb")
|
7
|
+
file = File.open("#{DOCS}#{file}.rb").read
|
8
|
+
Uv.parse(file, "xhtml", "ruby", false, "dawn", false)
|
9
|
+
end
|
10
|
+
%>
|
11
|
+
<!DOCTYPE html>
|
12
|
+
<html>
|
13
|
+
<head>
|
14
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
15
|
+
<title>Thinner -- Version <%= File.read('VERSION') %></title>
|
16
|
+
<link rel="stylesheet" type="text/css" href="documentation/css/styles.css" />
|
17
|
+
<link rel="stylesheet" type="text/css" href="documentation/css/dawn.css" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<a href="http://www.propublica.org" class="propublica"> </a>
|
21
|
+
<h1>Thinner – Version <%= File.read('VERSION') %></h1>
|
22
|
+
<p><a href="https://github.com/propublica/Thinner">Thinner</a> is a utility
|
23
|
+
for purging urls from a Varnish server.</p>
|
24
|
+
<p>When you are deploying code changes to a server under load, the uncached
|
25
|
+
load can quickly bring down your backend server even with Varnish's grace
|
26
|
+
mode. Often, allowing stale caches to stick around for a while saves
|
27
|
+
both server performance and sanity.</p>
|
28
|
+
<p>Thinner gives you fine-grained control over wildcard purging, and rolls
|
29
|
+
purges out slowly. Your users will see stale pages from the previous deploy
|
30
|
+
until Thinner has finished invalidating the stale cache at a rate that you set.
|
31
|
+
If you have a bunch of pages you need to invalidate en masse, but don't
|
32
|
+
want to risk overloading your server, Thinner is for you.</p>
|
33
|
+
<p>All that being said, Thinner isn't really a solution for observing
|
34
|
+
model changes and purging associated urls. If you have a highly dynamic
|
35
|
+
application, it's worlds better to handle purging via a
|
36
|
+
<a href="http://github.com/russ/lacquer/blob/master/lib/lacquer/delayed_job_job.rb">job server</a>
|
37
|
+
outside of the request-response flow.</p>
|
38
|
+
<p><a href="doc/index.html">API docs</a> | <a href="http://github.com/propublica/thinner/issues">Issue Tracker</a></p>
|
39
|
+
<h2>Installation</h2>
|
40
|
+
<p>Thinner is available via rubygems:
|
41
|
+
<pre>gem install thinner</pre>
|
42
|
+
<h2>Usage</h2>
|
43
|
+
<p>Thinner has both a library and command-line interface. To use it as a gem
|
44
|
+
you'll first have to configure how it works by calling <strong>Thinner.configure</strong>.
|
45
|
+
Here's a quick rundown of all of the options available:</p>
|
46
|
+
<%= code_for "configure" %>
|
47
|
+
<p>Once you have the configuration in place call <strong>purge!</strong> with
|
48
|
+
an array of urls:</p>
|
49
|
+
<%= code_for "purge" %>
|
50
|
+
<p>Thinner will then fork a background process and purge the urls. You can
|
51
|
+
check the progress of the purge by tailing the log file or with:</p>
|
52
|
+
<pre>varnishlog | grep purge</pre>
|
53
|
+
<p>If ruby's not your cup of tea, Thinner also has a command line interface.
|
54
|
+
Once you've installed the gem run <strong>thinner -h</strong> to see the
|
55
|
+
available options.</p>
|
56
|
+
<p>The command line interface accepts a newline separated list of urls via
|
57
|
+
stdin by setting the <strong>-e</strong> flag. So you'll be able to use
|
58
|
+
the command like so:</p>
|
59
|
+
<pre>cat urls_to_purge.txt | bin/thinner -e</pre>
|
60
|
+
<h2>Change Log</h2>
|
61
|
+
<h3>0.1.0</h3>
|
62
|
+
<p>Initial release.</p>
|
63
|
+
</body>
|
64
|
+
</html>
|
data/index.html
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
6
|
+
<title>Thinner -- Version 0.1.0
|
7
|
+
</title>
|
8
|
+
<link rel="stylesheet" type="text/css" href="documentation/css/styles.css" />
|
9
|
+
<link rel="stylesheet" type="text/css" href="documentation/css/dawn.css" />
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<a href="http://www.propublica.org" class="propublica"> </a>
|
13
|
+
<h1>Thinner – Version 0.1.0
|
14
|
+
</h1>
|
15
|
+
<p><a href="https://github.com/propublica/Thinner">Thinner</a> is a utility
|
16
|
+
for purging urls from a Varnish server.</p>
|
17
|
+
<p>When you are deploying code changes to a server under load, the uncached
|
18
|
+
load can quickly bring down your backend server even with Varnish's grace
|
19
|
+
mode. Often, allowing stale caches to stick around for a while saves
|
20
|
+
both server performance and sanity.</p>
|
21
|
+
<p>Thinner gives you fine-grained control over wildcard purging, and rolls
|
22
|
+
purges out slowly. Your users will see stale pages from the previous deploy
|
23
|
+
until Thinner has finished invalidating the stale cache at a rate that you set.
|
24
|
+
If you have a bunch of pages you need to invalidate en masse, but don't
|
25
|
+
want to risk overloading your server, Thinner is for you.</p>
|
26
|
+
<p>All that being said, Thinner isn't really a solution for observing
|
27
|
+
model changes and purging associated urls. If you have a highly dynamic
|
28
|
+
application, it's worlds better to handle purging via a
|
29
|
+
<a href="http://github.com/russ/lacquer/blob/master/lib/lacquer/delayed_job_job.rb">job server</a>
|
30
|
+
outside of the request-response flow.</p>
|
31
|
+
<p><a href="doc/index.html">API docs</a> | <a href="http://github.com/propublica/thinner/issues">Issue Tracker</a></p>
|
32
|
+
<h2>Installation</h2>
|
33
|
+
<p>Thinner is available via rubygems:
|
34
|
+
<pre>gem install thinner</pre>
|
35
|
+
<h2>Usage</h2>
|
36
|
+
<p>Thinner has both a library and command-line interface. To use it as a gem
|
37
|
+
you'll first have to configure how it works by calling <strong>Thinner.configure</strong>.
|
38
|
+
Here's a quick rundown of all of the options available:</p>
|
39
|
+
<pre class="dawn"><span class="Support">Thinner</span><span class="PunctuationSeparator">.</span><span class="Entity">configure</span> <span class="Keyword">do </span><span class="PunctuationSeparator">|</span><span class="Variable">config</span><span class="PunctuationSeparator">|</span>
|
40
|
+
<span class="Comment"> <span class="Comment">#</span> Number of urls to purge at one time. These purge requests are fired in quick</span>
|
41
|
+
<span class="Comment"> <span class="Comment">#</span> succession. Thinner is perfectly capable of killing a Varnish server, by</span>
|
42
|
+
<span class="Comment"> <span class="Comment">#</span> overloading the worker thread, so be really conservative with this option.</span>
|
43
|
+
config<span class="PunctuationSeparator">.</span><span class="Entity">batch_length</span> <span class="Keyword">=</span> <span class="Constant">10</span>
|
44
|
+
|
45
|
+
<span class="Comment"> <span class="Comment">#</span> The amount of time to sleep between purges in seconds.</span>
|
46
|
+
config<span class="PunctuationSeparator">.</span><span class="Entity">sleep_time</span> <span class="Keyword">=</span> <span class="Constant">1</span>
|
47
|
+
|
48
|
+
<span class="Comment"> <span class="Comment">#</span> The server address and management port. See:</span>
|
49
|
+
<span class="Comment"> <span class="Comment">#</span> http://www.varnish-cache.org/trac/wiki/ManagementPort</span>
|
50
|
+
<span class="Comment"> <span class="Comment">#</span> for details.</span>
|
51
|
+
config<span class="PunctuationSeparator">.</span><span class="Entity">server</span> <span class="Keyword">=</span> "127.0.0.1:6082"
|
52
|
+
|
53
|
+
<span class="Comment"> <span class="Comment">#</span> By default, every time you call Thinner.purge! thinner spins off a new</span>
|
54
|
+
<span class="Comment"> <span class="Comment">#</span> instance of Thinner::Client and terminates any old instances that are</span>
|
55
|
+
<span class="Comment"> <span class="Comment">#</span> running. If you want to have overlapping instances set this to true.</span>
|
56
|
+
<span class="Comment"> <span class="Comment">#</span> It's not recommended to have multiple Thinner::Client's running at the</span>
|
57
|
+
<span class="Comment"> <span class="Comment">#</span> same time.</span>
|
58
|
+
config<span class="PunctuationSeparator">.</span><span class="Entity">no_kill</span> <span class="Keyword">=</span> <span class="Constant">false</span>
|
59
|
+
|
60
|
+
<span class="Comment"> <span class="Comment">#</span> The log file (either a string or file object) to log the current batch to.</span>
|
61
|
+
<span class="Comment"> <span class="Comment">#</span> Defaults to STDOUT</span>
|
62
|
+
config<span class="PunctuationSeparator">.</span><span class="Entity">log_file</span> <span class="Keyword">=</span> <span class="Variable">STDOUT</span>
|
63
|
+
<span class="Keyword">end</span>
|
64
|
+
</pre>
|
65
|
+
<p>Once you have the configuration in place call <strong>purge!</strong> with
|
66
|
+
an array of urls:</p>
|
67
|
+
<pre class="dawn"><span class="Comment"><span class="Comment">#</span> The urls in this array are purged in order, so you'll want to structure it</span>
|
68
|
+
<span class="Comment"><span class="Comment">#</span> according to usage.</span>
|
69
|
+
arr <span class="Keyword"><<</span> ["/some_route"<span class="PunctuationSeparator">,</span> "/"]
|
70
|
+
|
71
|
+
<span class="Support">Thinner</span><span class="PunctuationSeparator">.</span><span class="Entity">purge!</span> arr
|
72
|
+
</pre>
|
73
|
+
<p>Thinner will then fork a background process and purge the urls. You can
|
74
|
+
check the progress of the purge by tailing the log file or with:</p>
|
75
|
+
<pre>varnishlog | grep purge</pre>
|
76
|
+
<p>If ruby's not your cup of tea, Thinner also has a command line interface.
|
77
|
+
Once you've installed the gem run <strong>thinner -h</strong> to see the
|
78
|
+
available options.</p>
|
79
|
+
<p>The command line interface accepts a newline separated list of urls via
|
80
|
+
stdin by setting the <strong>-e</strong> flag. So you'll be able to use
|
81
|
+
the command like so:</p>
|
82
|
+
<pre>cat urls_to_purge.txt | bin/thinner -e</pre>
|
83
|
+
<h2>Change Log</h2>
|
84
|
+
<h3>0.1.0</h3>
|
85
|
+
<p>Initial release.</p>
|
86
|
+
</body>
|
87
|
+
</html>
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Thinner
|
4
|
+
|
5
|
+
# A Thinner::Client runs as a background process and purges a list of urls
|
6
|
+
# in batches.
|
7
|
+
class Client
|
8
|
+
|
9
|
+
# A list of successfully purged urls.
|
10
|
+
attr_reader :purged_urls
|
11
|
+
|
12
|
+
# The list of Errors we want to catch.
|
13
|
+
ERRORS = [Varnish::Error, Varnish::BrokenConnection, Varnish::CommandFailed, Timeout::Error, Errno::ECONNREFUSED]
|
14
|
+
|
15
|
+
# Before purging, each Thinner::Client grabs various configuration settings
|
16
|
+
# and makes a copy of the passed in urls.
|
17
|
+
def initialize(urls)
|
18
|
+
@batch = Thinner.configuration.batch_length
|
19
|
+
@timeout = Thinner.configuration.sleep_time
|
20
|
+
@varnish = Varnish::Client.new Thinner.configuration.server
|
21
|
+
@log_file = Thinner.configuration.log_file
|
22
|
+
@purged_urls = []
|
23
|
+
@urls = Array.new urls
|
24
|
+
@length = @urls.length
|
25
|
+
logger
|
26
|
+
handle_errors
|
27
|
+
end
|
28
|
+
|
29
|
+
# Kickstart the purging process and loop through the array until there aren't
|
30
|
+
# any urls left to purge. Each time the loop runs it will update the process
|
31
|
+
# label with the first url in the list.
|
32
|
+
def run!
|
33
|
+
while @urls.length > 0
|
34
|
+
@current_job = @urls.slice! 0, @batch
|
35
|
+
$0 = "#{PROCESS_IDENTIFIER}: purging #{@current_job.first}"
|
36
|
+
purge_urls
|
37
|
+
sleep @timeout
|
38
|
+
end
|
39
|
+
close_log
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Once a batch is ready the Client fires off purge requests on the list of
|
45
|
+
# urls.
|
46
|
+
def purge_urls
|
47
|
+
@current_job.each do |url|
|
48
|
+
begin
|
49
|
+
@varnish.start if @varnish.stopped?
|
50
|
+
while(!@varnish.running?) do sleep 0.1 end
|
51
|
+
if @varnish.purge :url, url
|
52
|
+
@logger.info "Purged url: #{url}"
|
53
|
+
@purged_urls << url
|
54
|
+
else
|
55
|
+
@logger.warn "Could not purge: #{url}"
|
56
|
+
end
|
57
|
+
rescue *ERRORS => e
|
58
|
+
@logger.warn "Error on url: #{url}, message: #{e}"
|
59
|
+
sleep @timeout
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Trap certain signals so the Client can report back the progress of the
|
65
|
+
# job and close the log.
|
66
|
+
def handle_errors
|
67
|
+
trap('TERM') { close_log }
|
68
|
+
trap('KILL') { close_log }
|
69
|
+
trap('INT') { close_log }
|
70
|
+
end
|
71
|
+
|
72
|
+
# The logger redirects all STDOUT writes to a logger instance.
|
73
|
+
def logger
|
74
|
+
if !@log_file.respond_to?(:write)
|
75
|
+
STDOUT.reopen(File.open(@log_file, (File::WRONLY | File::APPEND | File::CREAT)))
|
76
|
+
end
|
77
|
+
@logger = Logger.new(STDOUT)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Log the purged urls and exit the process.
|
81
|
+
def close_log
|
82
|
+
@logger.info "Purged #{@purged_urls.length} of #{@length} urls."
|
83
|
+
@logger.info "Exiting..."
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require File.expand_path("#{File.dirname __FILE__}/../thinner.rb")
|
3
|
+
|
4
|
+
module Thinner
|
5
|
+
|
6
|
+
class CommandLine
|
7
|
+
|
8
|
+
# Usage and summary
|
9
|
+
BANNER = <<-EOF
|
10
|
+
Thinner purges varnish caches as slowly as you need it to.
|
11
|
+
|
12
|
+
Documentation: http://propublica.github.com/thinner/
|
13
|
+
|
14
|
+
Usage: thinner OPTIONS URL
|
15
|
+
|
16
|
+
Options:
|
17
|
+
EOF
|
18
|
+
# Create a Thinner::CommandLine, parse any associated options, grab a list
|
19
|
+
# of urls and start the process
|
20
|
+
def initialize
|
21
|
+
@urls = []
|
22
|
+
options!
|
23
|
+
@urls ||= ARGV
|
24
|
+
run!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Build a Thinner::Configuration instance from the passed in options and go
|
28
|
+
# to the races.
|
29
|
+
def run!
|
30
|
+
Thinner.configure do |config|
|
31
|
+
@options.each_pair do |key, value|
|
32
|
+
config.send("#{key}=".to_sym, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
Thinner.purge! @urls
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Parse the command line options using OptionParser.
|
41
|
+
def options!
|
42
|
+
@options = {}
|
43
|
+
@option_parser = OptionParser.new(BANNER) do |opts|
|
44
|
+
opts.on("-b", "--batch_length BATCH", "Number of urls to purge at once") do |b|
|
45
|
+
@options[:batch_length] = b.to_i
|
46
|
+
end
|
47
|
+
opts.on("-t", "--sleep_time SLEEP", "Time to wait in between batches") do |t|
|
48
|
+
@options[:sleep_time] = t.to_i
|
49
|
+
end
|
50
|
+
opts.on("-e", "--stdin", "Use stdin for urls") do
|
51
|
+
ARGF.each_line do |url|
|
52
|
+
@urls << url.chomp
|
53
|
+
end
|
54
|
+
end
|
55
|
+
opts.on("-s", "--server SERVER", "Varnish url, e.g. 127.0.0.1:6082") do |s|
|
56
|
+
@options[:server] = s
|
57
|
+
end
|
58
|
+
opts.on("-o", "--log_file LOG_PATH", "Log file to output to (default: Standard Out") do |o|
|
59
|
+
@options[:log_file] = o
|
60
|
+
end
|
61
|
+
opts.on("-n", "--no-kill", "Don't kill the running purgers if they exist") do |n|
|
62
|
+
@options[:no_kill] = n
|
63
|
+
end
|
64
|
+
opts.on_tail("-h", "--help", "Display this help message") do
|
65
|
+
puts opts.help
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
@option_parser.parse!(ARGV)
|
72
|
+
rescue OptionParser::InvalidOption => e
|
73
|
+
puts e.message
|
74
|
+
exit(1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Thinner
|
2
|
+
|
3
|
+
# Thinner::Configuration holds the various settings for Thinner
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
attr_accessor :batch_length, :sleep_time, :server, :log_file, :no_kill
|
7
|
+
|
8
|
+
# Create a Thinner::Configuration instance with sane defaults.
|
9
|
+
def initialize
|
10
|
+
# Number of urls to purge at one time. These purge requests are fired in quick
|
11
|
+
# succession. Thinner is perfectly capable of killing a Varnish server, by
|
12
|
+
# overloading the worker thread, so be really conservative with this option.
|
13
|
+
@batch_length = 10
|
14
|
+
|
15
|
+
# The amount of time to sleep between purges in seconds.
|
16
|
+
@sleep_time = 1
|
17
|
+
|
18
|
+
# The server address and management port. See:
|
19
|
+
# http://www.varnish-cache.org/trac/wiki/ManagementPort
|
20
|
+
# for details.
|
21
|
+
@server = "127.0.0.1:6082"
|
22
|
+
|
23
|
+
# By default, every time you call Thinner.purge! thinner spins off a new
|
24
|
+
# instance of Thinner::Client and terminates any old instances that are
|
25
|
+
# running. If you want to have overlapping instances set this to true.
|
26
|
+
# It's not recommended to have multiple Thinner::Client's running at the
|
27
|
+
# same time.
|
28
|
+
@no_kill = false
|
29
|
+
|
30
|
+
# The log file (either a string or file object) to log the current batch to.
|
31
|
+
# Defaults to STDOUT
|
32
|
+
@log_file = STDOUT
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Thinner
|
2
|
+
|
3
|
+
# A Thinner::Purger dispatches a client and ensures only one instance of a
|
4
|
+
# Thinner::Client is running at a given time.
|
5
|
+
class Purger
|
6
|
+
|
7
|
+
# Each Purger accepts a list of urls to pass on to the client to purge.
|
8
|
+
def initialize(urls)
|
9
|
+
@urls = urls
|
10
|
+
end
|
11
|
+
|
12
|
+
# After the configuration is in place and the Purger has a list of urls,
|
13
|
+
# it can fork a client process to run in the background. By default the
|
14
|
+
# Purger will kill any old Thinner::Client processes still running so as
|
15
|
+
# to not double up on purge requests.
|
16
|
+
def purge!
|
17
|
+
self.class.stop! unless Thinner.configuration.no_kill
|
18
|
+
puts "==== Starting purge see: #{Thinner.configuration.log_file} for finished urls."
|
19
|
+
client_id = fork {
|
20
|
+
Client.new(@urls).run!
|
21
|
+
}
|
22
|
+
Process.detach(client_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
# A list of Thinner::Client process ids -- adapted from resque.
|
26
|
+
def self.job_ids
|
27
|
+
lines = `ps -A -o pid,command | grep #{PROCESS_IDENTIFIER}`.split("\n").map do |line|
|
28
|
+
line.split(' ')[0].to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Before we spin up a new client each running process is killed by pid. Each
|
33
|
+
# killed process's id is logged in the Thinner log file.
|
34
|
+
def self.stop!
|
35
|
+
job_ids.each do |pid|
|
36
|
+
begin
|
37
|
+
Process.kill("KILL", pid.to_i)
|
38
|
+
puts "==== Killing process: #{pid}"
|
39
|
+
rescue Errno::ESRCH
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/thinner.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Thinner
|
2
|
+
|
3
|
+
# The base location of the Thinner gem.
|
4
|
+
ROOT = File.expand_path "#{File.dirname __FILE__}/.."
|
5
|
+
|
6
|
+
# The Thinner version.
|
7
|
+
VERSION = File.read("#{ROOT}/VERSION").chomp
|
8
|
+
|
9
|
+
# The process label to run each Thinner::Client under.
|
10
|
+
PROCESS_IDENTIFIER = "Thinner"
|
11
|
+
|
12
|
+
# Set up the configuration instance as a class level accessor.
|
13
|
+
class << self; attr_accessor :configuration; end
|
14
|
+
|
15
|
+
# Set any thinner settings by passing in a block.
|
16
|
+
def self.configure
|
17
|
+
self.configuration ||= Configuration.new
|
18
|
+
yield configuration
|
19
|
+
end
|
20
|
+
|
21
|
+
# Halt any running instances of Thinner::Client
|
22
|
+
def self.stop!
|
23
|
+
Purger.stop!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Begin purging urls.
|
27
|
+
def self.purge! urls
|
28
|
+
Purger.new(urls).purge!
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
require "klarlack"
|
34
|
+
require "logger"
|
35
|
+
require "#{Thinner::ROOT}/lib/thinner/configuration"
|
36
|
+
require "#{Thinner::ROOT}/lib/thinner/client"
|
37
|
+
require "#{Thinner::ROOT}/lib/thinner/purger"
|
data/test/helper.rb
ADDED