thinner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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