aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Lars Hjemli <hjemli@gmail.com>2006-12-11 06:31:36 (JST)
committerGravatar Lars Hjemli <hjemli@gmail.com>2006-12-11 06:31:36 (JST)
commit25105d7ecaba474d4b7c364ebb586aac3dfc5abb (patch)
tree8beb08db1399b8efb8c7fbcd936044ae7fc232e6
parent856c026e221d8ed82c5b75bc8da4bd65e89ea953 (diff)
downloadcgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.zip
cgit-25105d7ecaba474d4b7c364ebb586aac3dfc5abb.tar.gz
Add caching infrastructure
This enables internal caching of page output. Page requests are split into four groups: 1) repo listing (front page) 2) repo summary 3) repo pages w/symbolic references in query string 4) repo pages w/constant sha1's in query string Each group has a TTL specified in minutes. When a page is requested, a cached filename is stat(2)'ed and st_mtime is compared to time(2). If TTL has expired (or the file didn't exist), the cached file is regenerated. When generating a cached file, locking is used to avoid parallell processing of the request. If multiple processes tries to aquire the same lock, the ones who fail to get the lock serves the (expired) cached file. If the cached file don't exist, the process instead calls sched_yield(2) before restarting the request processing. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile6
-rw-r--r--README54
-rw-r--r--cache.c86
-rw-r--r--cgit.c117
-rw-r--r--cgit.h47
-rw-r--r--config.c4
-rw-r--r--git.h60
-rw-r--r--html.c6
9 files changed, 353 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index 4eaec97..c4c9ac3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
1# Files I don't care to see in git-status/commit 1# Files I don't care to see in git-status/commit
2cgit 2cgit
3*.o 3*.o
4*~
diff --git a/Makefile b/Makefile
index 4e72b07..243f590 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,9 @@ INSTALL_BIN = /var/www/htdocs/cgit.cgi
3INSTALL_CSS = /var/www/htdocs/cgit.css 3INSTALL_CSS = /var/www/htdocs/cgit.css
4 4
5EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto 5EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto
6OBJECTS = cgit.o config.o html.o 6OBJECTS = cgit.o config.o html.o cache.o
7
8CFLAGS += -Wall
7 9
8all: cgit 10all: cgit
9 11
@@ -15,6 +17,6 @@ clean:
15 rm -f cgit *.o 17 rm -f cgit *.o
16 18
17cgit: $(OBJECTS) 19cgit: $(OBJECTS)
18 $(CC) -o cgit $(OBJECTS) $(EXTLIBS) 20 $(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
19 21
20$(OBJECTS): cgit.h git.h 22$(OBJECTS): cgit.h git.h
diff --git a/README b/README
new file mode 100644
index 0000000..5917c37
--- /dev/null
+++ b/README
@@ -0,0 +1,54 @@
1Cache algorithm
2===============
3
4Cgit normally returns cached pages when invoked. If there is no cache file, or
5the cache file has expired, it is regenerated. Finally, the cache file is
6printed on stdout.
7
8When it is decided that a cache file needs to be regenerated, an attempt is
9made to create a corresponding lockfile. If this fails, the process gives up
10and uses the expired cache file instead.
11
12When there is no cache file for a request, an attempt is made to create a
13corresponding lockfile. If this fails, the process calls sched_yield(2) before
14restarting the request handling.
15
16In pseudocode:
17
18 name = generate_cache_name(request);
19top:
20 if (!exists(name)) {
21 if (lock_cache(name)) {
22 generate_cache(request, name);
23 unlock_cache(name);
24 } else {
25 sched_yield();
26 goto top;
27 }
28 } else if (expired(name)) {
29 if (lock_cache(name)) {
30 generate_cache(request, name);
31 unlock_cache(name);
32 }
33 }
34 print_file(name);
35
36
37The following options can be set in /etc/cgitrc to control cache behaviour:
38 cache-root: root directory for cache files
39 cache-root-ttl: TTL for the repo listing page
40 cache-repo-ttl: TTL for any repos summary page
41 cache-dynamic-ttl: TTL for pages with symbolic references (not SHA1)
42 cache-static-ttl: TTL for pages with sha1 references
43
44TTL is specified in minutes, -1 meaning "infinite caching".
45
46
47Naming of cache files
48---------------------
49Repository listing: <cachedir>/index.html
50Repository summary: <cachedir>/<repo>/index.html
51Repository subpage: <cachedir>/<repo>/<page>/<querystring>.html
52
53The corresponding lock files have a ".lock" suffix.
54
diff --git a/cache.c b/cache.c
new file mode 100644
index 0000000..1be1ea4
--- /dev/null
+++ b/cache.c
@@ -0,0 +1,86 @@
1#include "cgit.h"
2
3const int NOLOCK = -1;
4
5int cache_lookup(struct cacheitem *item)
6{
7 if (!cgit_query_repo) {
8 item->name = xstrdup(fmt("%s/index.html", cgit_cache_root));
9 item->ttl = cgit_cache_root_ttl;
10 } else if (!cgit_query_page) {
11 item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root,
12 cgit_query_repo));
13 item->ttl = cgit_cache_repo_ttl;
14 } else {
15 item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root,
16 cgit_query_repo, cgit_query_page,
17 cgit_querystring));
18 if (cgit_query_has_symref)
19 item->ttl = cgit_cache_dynamic_ttl;
20 else if (cgit_query_has_sha1)
21 item->ttl = cgit_cache_static_ttl;
22 else
23 item->ttl = cgit_cache_repo_ttl;
24 }
25 if (stat(item->name, &item->st)) {
26 item->st.st_mtime = 0;
27 return 0;
28 }
29 return 1;
30}
31
32int cache_create_dirs()
33{
34 char *path;
35
36 if (!cgit_query_repo)
37 return 0;
38
39 path = fmt("%s/%s", cgit_cache_root, cgit_query_repo);
40 if (mkdir(path, S_IRWXU) && errno!=EEXIST)
41 return 0;
42
43 if (cgit_query_page) {
44 path = fmt("%s/%s/%s", cgit_cache_root, cgit_query_repo,
45 cgit_query_page);
46 if (mkdir(path, S_IRWXU) && errno!=EEXIST)
47 return 0;
48 }
49 return 1;
50}
51
52int cache_lock(struct cacheitem *item)
53{
54 int ret;
55 char *lockfile = fmt("%s.lock", item->name);
56
57 top:
58 item->fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR|S_IWUSR);
59 if (item->fd == NOLOCK && errno == ENOENT && cache_create_dirs())
60 goto top;
61 if (item->fd == NOLOCK && errno == EEXIST) {
62 struct stat st;
63 time_t t;
64 if (stat(lockfile, &st))
65 return ret;
66 t = time(NULL);
67 if (t-st.st_mtime > cgit_cache_max_create_time &&
68 !unlink(lockfile))
69 goto top;
70 return 0;
71 }
72 return (item->fd > 0);
73}
74
75int cache_unlock(struct cacheitem *item)
76{
77 close(item->fd);
78 return (rename(fmt("%s.lock", item->name), item->name) == 0);
79}
80
81int cache_expired(struct cacheitem *item)
82{
83 if (item->ttl < 0)
84 return 0;
85 return item->st.st_mtime + item->ttl * 60 < time(NULL);
86}
diff --git a/cgit.c b/cgit.c
index 4c14f77..09c857c 100644
--- a/cgit.c
+++ b/cgit.c
@@ -10,29 +10,47 @@ static const char cgit_error[] =
10static const char cgit_lib_error[] = 10static const char cgit_lib_error[] =
11"<div class='error'>%s: %s</div>"; 11"<div class='error'>%s: %s</div>";
12 12
13int htmlfd = 0;
13 14
14char *cgit_root = "/var/git"; 15char *cgit_root = "/usr/src/git";
15char *cgit_root_title = "Git repository browser"; 16char *cgit_root_title = "Git repository browser";
16char *cgit_css = "/cgit.css"; 17char *cgit_css = "/cgit.css";
17char *cgit_logo = "/git-logo.png"; 18char *cgit_logo = "/git-logo.png";
18char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; 19char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/";
19char *cgit_virtual_root = NULL; 20char *cgit_virtual_root = NULL;
20 21
22char *cgit_cache_root = "/var/cache/cgit";
23
24int cgit_cache_root_ttl = 5;
25int cgit_cache_repo_ttl = 5;
26int cgit_cache_dynamic_ttl = 5;
27int cgit_cache_static_ttl = -1;
28int cgit_cache_max_create_time = 5;
29
21char *cgit_repo_name = NULL; 30char *cgit_repo_name = NULL;
22char *cgit_repo_desc = NULL; 31char *cgit_repo_desc = NULL;
23char *cgit_repo_owner = NULL; 32char *cgit_repo_owner = NULL;
24 33
34int cgit_query_has_symref = 0;
35int cgit_query_has_sha1 = 0;
36
37char *cgit_querystring = NULL;
25char *cgit_query_repo = NULL; 38char *cgit_query_repo = NULL;
26char *cgit_query_page = NULL; 39char *cgit_query_page = NULL;
27char *cgit_query_head = NULL; 40char *cgit_query_head = NULL;
41char *cgit_query_sha1 = NULL;
42
43struct cacheitem cacheitem;
28 44
29int cgit_parse_query(char *txt, configfn fn) 45int cgit_parse_query(char *txt, configfn fn)
30{ 46{
31 char *t = txt, *value = NULL, c; 47 char *t, *value = NULL, c;
32 48
33 if (!txt) 49 if (!txt)
34 return 0; 50 return 0;
35 51
52 t = txt = xstrdup(txt);
53
36 while((c=*t) != '\0') { 54 while((c=*t) != '\0') {
37 if (c=='=') { 55 if (c=='=') {
38 *t = '\0'; 56 *t = '\0';
@@ -82,8 +100,13 @@ void cgit_querystring_cb(const char *name, const char *value)
82 cgit_query_repo = xstrdup(value); 100 cgit_query_repo = xstrdup(value);
83 else if (!strcmp(name, "p")) 101 else if (!strcmp(name, "p"))
84 cgit_query_page = xstrdup(value); 102 cgit_query_page = xstrdup(value);
85 else if (!strcmp(name, "h")) 103 else if (!strcmp(name, "h")) {
86 cgit_query_head = xstrdup(value); 104 cgit_query_head = xstrdup(value);
105 cgit_query_has_symref = 1;
106 } else if (!strcmp(name, "id")) {
107 cgit_query_sha1 = xstrdup(value);
108 cgit_query_has_sha1 = 1;
109 }
87} 110}
88 111
89char *cgit_repourl(const char *reponame) 112char *cgit_repourl(const char *reponame)
@@ -136,9 +159,32 @@ static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
136 return 0; 159 return 0;
137} 160}
138 161
162/* Sun, 06 Nov 1994 08:49:37 GMT */
163static char *http_date(time_t t)
164{
165 static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
166 static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
167 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
168 struct tm *tm = gmtime(&t);
169 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
170 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
171 tm->tm_hour, tm->tm_min, tm->tm_sec);
172}
173
174static int ttl_seconds(int ttl)
175{
176 if (ttl<0)
177 return 60 * 60 * 24 * 365;
178 else
179 return ttl * 60;
180}
181
139static void cgit_print_docstart(char *title) 182static void cgit_print_docstart(char *title)
140{ 183{
141 html("Content-Type: text/html; charset=utf-8\n"); 184 html("Content-Type: text/html; charset=utf-8\n");
185 htmlf("Last-Modified: %s\n", http_date(cacheitem.st.st_mtime));
186 htmlf("Expires: %s\n", http_date(cacheitem.st.st_mtime +
187 ttl_seconds(cacheitem.ttl)));
142 html("\n"); 188 html("\n");
143 html(cgit_doctype); 189 html(cgit_doctype);
144 html("<html>\n"); 190 html("<html>\n");
@@ -175,6 +221,7 @@ static void cgit_print_repolist()
175 struct stat st; 221 struct stat st;
176 char *name; 222 char *name;
177 223
224 chdir(cgit_root);
178 cgit_print_docstart(cgit_root_title); 225 cgit_print_docstart(cgit_root_title);
179 cgit_print_pageheader(cgit_root_title); 226 cgit_print_pageheader(cgit_root_title);
180 227
@@ -197,7 +244,7 @@ static void cgit_print_repolist()
197 continue; 244 continue;
198 245
199 cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL; 246 cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL;
200 name = fmt("%s/.git/info/cgit", de->d_name); 247 name = fmt("%s/info/cgit", de->d_name);
201 if (cgit_read_config(name, cgit_repo_config_cb)) 248 if (cgit_read_config(name, cgit_repo_config_cb))
202 continue; 249 continue;
203 250
@@ -291,7 +338,7 @@ static void cgit_print_commit_shortlog(struct commit *commit)
291 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); 338 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time);
292 html_txt(buf); 339 html_txt(buf);
293 html("</td><td>"); 340 html("</td><td>");
294 char *qry = fmt("h=%s", sha1_to_hex(commit->object.sha1)); 341 char *qry = fmt("id=%s", sha1_to_hex(commit->object.sha1));
295 char *url = cgit_pageurl(cgit_query_repo, "view", qry); 342 char *url = cgit_pageurl(cgit_query_repo, "view", qry);
296 html_link_open(url, NULL, NULL); 343 html_link_open(url, NULL, NULL);
297 html_txt(subject); 344 html_txt(subject);
@@ -371,8 +418,8 @@ static void cgit_print_object(char *hex)
371 418
372static void cgit_print_repo_page() 419static void cgit_print_repo_page()
373{ 420{
374 if (chdir(cgit_query_repo) || 421 if (chdir(fmt("%s/%s", cgit_root, cgit_query_repo)) ||
375 cgit_read_config(".git/info/cgit", cgit_repo_config_cb)) { 422 cgit_read_config("info/cgit", cgit_repo_config_cb)) {
376 char *title = fmt("%s - %s", cgit_root_title, "Bad request"); 423 char *title = fmt("%s - %s", cgit_root_title, "Bad request");
377 cgit_print_docstart(title); 424 cgit_print_docstart(title);
378 cgit_print_pageheader(title); 425 cgit_print_pageheader(title);
@@ -381,7 +428,7 @@ static void cgit_print_repo_page()
381 cgit_print_docend(); 428 cgit_print_docend();
382 return; 429 return;
383 } 430 }
384 431 setenv("GIT_DIR", fmt("%s/%s", cgit_root, cgit_query_repo), 1);
385 char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); 432 char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc);
386 cgit_print_docstart(title); 433 cgit_print_docstart(title);
387 cgit_print_pageheader(title); 434 cgit_print_pageheader(title);
@@ -390,21 +437,61 @@ static void cgit_print_repo_page()
390 else if (!strcmp(cgit_query_page, "log")) { 437 else if (!strcmp(cgit_query_page, "log")) {
391 cgit_print_log(cgit_query_head, 0, 100); 438 cgit_print_log(cgit_query_head, 0, 100);
392 } else if (!strcmp(cgit_query_page, "view")) { 439 } else if (!strcmp(cgit_query_page, "view")) {
393 cgit_print_object(cgit_query_head); 440 cgit_print_object(cgit_query_sha1);
394 } 441 }
395 cgit_print_docend(); 442 cgit_print_docend();
396} 443}
397 444
398int main(int argc, const char **argv) 445static void cgit_fill_cache(struct cacheitem *item)
399{ 446{
400 if (cgit_read_config("/etc/cgitrc", cgit_global_config_cb)) 447 htmlfd = item->fd;
401 die("Error reading config: %d %s", errno, strerror(errno)); 448 item->st.st_mtime = time(NULL);
402
403 chdir(cgit_root);
404 cgit_parse_query(getenv("QUERY_STRING"), cgit_querystring_cb);
405 if (cgit_query_repo) 449 if (cgit_query_repo)
406 cgit_print_repo_page(); 450 cgit_print_repo_page();
407 else 451 else
408 cgit_print_repolist(); 452 cgit_print_repolist();
453}
454
455static void cgit_refresh_cache(struct cacheitem *item)
456{
457 top:
458 if (!cache_lookup(item)) {
459 if (cache_lock(item)) {
460 cgit_fill_cache(item);
461 cache_unlock(item);
462 } else {
463 sched_yield();
464 goto top;
465 }
466 } else if (cache_expired(item)) {
467 if (cache_lock(item)) {
468 cgit_fill_cache(item);
469 cache_unlock(item);
470 }
471 }
472}
473
474static void cgit_print_cache(struct cacheitem *item)<