Skip to content

Commit 7046567

Browse files
committed
Initial import
0 parents  commit 7046567

File tree

9 files changed

+403
-0
lines changed

9 files changed

+403
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
.project
3+
.classpath
4+
.idea
5+
.cache
6+
.DS_Store
7+
*.im?
8+
target
9+
work
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.apt.aptEnabled=false
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
3+
org.eclipse.jdt.core.compiler.compliance=1.7
4+
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
5+
org.eclipse.jdt.core.compiler.processAnnotations=disabled
6+
org.eclipse.jdt.core.compiler.release=disabled
7+
org.eclipse.jdt.core.compiler.source=1.7
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
activeProfiles=
2+
eclipse.preferences.version=1
3+
resolveWorkspaceProjects=true
4+
version=1

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM maven:3.6-jdk-8-alpine as builder
2+
WORKDIR /code
3+
COPY pom.xml /code/pom.xml
4+
RUN ["mvn", "dependency:resolve"]
5+
RUN ["mvn", "verify"]
6+
# Adding source, compile and package into a fat jar
7+
COPY ["src/main", "/code/src/main"]
8+
RUN ["mvn", "package"]
9+
10+
FROM openjdk:8-jre-alpine
11+
COPY --from=builder /code/target/worker-jar-with-dependencies.jar /
12+
CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-jar", "/worker-jar-with-dependencies.jar"]

pom.xml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>worker</groupId>
7+
<artifactId>worker</artifactId>
8+
<version>1.0-SNAPSHOT</version>
9+
10+
11+
<dependencies>
12+
<dependency>
13+
<groupId>org.json</groupId>
14+
<artifactId>json</artifactId>
15+
<version>20140107</version>
16+
</dependency>
17+
18+
<dependency>
19+
<groupId>redis.clients</groupId>
20+
<artifactId>jedis</artifactId>
21+
<version>2.7.2</version>
22+
<type>jar</type>
23+
<scope>compile</scope>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>org.postgresql</groupId>
28+
<artifactId>postgresql</artifactId>
29+
<version>9.4-1200-jdbc41</version>
30+
</dependency>
31+
</dependencies>
32+
33+
<build>
34+
<plugins>
35+
<plugin>
36+
<groupId>org.apache.maven.plugins</groupId>
37+
<artifactId>maven-jar-plugin</artifactId>
38+
<version>2.4</version>
39+
<configuration>
40+
<finalName>worker</finalName>
41+
<archive>
42+
<manifest>
43+
<addClasspath>true</addClasspath>
44+
<mainClass>worker.Worker</mainClass>
45+
<classpathPrefix>dependency-jars/</classpathPrefix>
46+
</manifest>
47+
</archive>
48+
</configuration>
49+
</plugin>
50+
<plugin>
51+
<groupId>org.apache.maven.plugins</groupId>
52+
<artifactId>maven-compiler-plugin</artifactId>
53+
<version>3.1</version>
54+
<configuration>
55+
<source>1.7</source>
56+
<target>1.7</target>
57+
</configuration>
58+
</plugin>
59+
<plugin>
60+
<groupId>org.apache.maven.plugins</groupId>
61+
<artifactId>maven-assembly-plugin</artifactId>
62+
<executions>
63+
<execution>
64+
<goals>
65+
<goal>attached</goal>
66+
</goals>
67+
<phase>package</phase>
68+
<configuration>
69+
<finalName>worker</finalName>
70+
<descriptorRefs>
71+
<descriptorRef>jar-with-dependencies</descriptorRef>
72+
</descriptorRefs>
73+
<archive>
74+
<manifest>
75+
<mainClass>worker.Worker</mainClass>
76+
</manifest>
77+
</archive>
78+
</configuration>
79+
</execution>
80+
</executions>
81+
</plugin>
82+
83+
<plugin>
84+
<groupId>org.apache.maven.plugins</groupId>
85+
<artifactId>maven-deploy-plugin</artifactId>
86+
<version>2.8.2</version></plugin>
87+
88+
<plugin>
89+
<groupId>org.apache.maven.plugins</groupId>
90+
<artifactId>maven-surefire-plugin</artifactId>
91+
<version>3.0.0-M1</version></plugin>
92+
</plugins>
93+
</build>
94+
</project>

src/Worker/Program.cs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
using System;
2+
using System.Data.Common;
3+
using System.Linq;
4+
using System.Net;
5+
using System.Net.Sockets;
6+
using System.Threading;
7+
using Newtonsoft.Json;
8+
using Npgsql;
9+
using StackExchange.Redis;
10+
11+
namespace Worker
12+
{
13+
public class Program
14+
{
15+
public static int Main(string[] args)
16+
{
17+
try
18+
{
19+
var pgsql = OpenDbConnection("Server=db;Username=postgres;");
20+
var redisConn = OpenRedisConnection("redis");
21+
var redis = redisConn.GetDatabase();
22+
23+
// Keep alive is not implemented in Npgsql yet. This workaround was recommended:
24+
// https://github.com/npgsql/npgsql/issues/1214#issuecomment-235828359
25+
var keepAliveCommand = pgsql.CreateCommand();
26+
keepAliveCommand.CommandText = "SELECT 1";
27+
28+
var definition = new { vote = "", voter_id = "" };
29+
while (true)
30+
{
31+
// Slow down to prevent CPU spike, only query each 100ms
32+
Thread.Sleep(100);
33+
34+
// Reconnect redis if down
35+
if (redisConn == null || !redisConn.IsConnected) {
36+
Console.WriteLine("Reconnecting Redis");
37+
redisConn = OpenRedisConnection("redis");
38+
redis = redisConn.GetDatabase();
39+
}
40+
string json = redis.ListLeftPopAsync("votes").Result;
41+
if (json != null)
42+
{
43+
var vote = JsonConvert.DeserializeAnonymousType(json, definition);
44+
Console.WriteLine($"Processing vote for '{vote.vote}' by '{vote.voter_id}'");
45+
// Reconnect DB if down
46+
if (!pgsql.State.Equals(System.Data.ConnectionState.Open))
47+
{
48+
Console.WriteLine("Reconnecting DB");
49+
pgsql = OpenDbConnection("Server=db;Username=postgres;");
50+
}
51+
else
52+
{ // Normal +1 vote requested
53+
UpdateVote(pgsql, vote.voter_id, vote.vote);
54+
}
55+
}
56+
else
57+
{
58+
keepAliveCommand.ExecuteNonQuery();
59+
}
60+
}
61+
}
62+
catch (Exception ex)
63+
{
64+
Console.Error.WriteLine(ex.ToString());
65+
return 1;
66+
}
67+
}
68+
69+
private static NpgsqlConnection OpenDbConnection(string connectionString)
70+
{
71+
NpgsqlConnection connection;
72+
73+
while (true)
74+
{
75+
try
76+
{
77+
connection = new NpgsqlConnection(connectionString);
78+
connection.Open();
79+
break;
80+
}
81+
catch (SocketException)
82+
{
83+
Console.Error.WriteLine("Waiting for db");
84+
Thread.Sleep(1000);
85+
}
86+
catch (DbException)
87+
{
88+
Console.Error.WriteLine("Waiting for db");
89+
Thread.Sleep(1000);
90+
}
91+
}
92+
93+
Console.Error.WriteLine("Connected to db");
94+
95+
var command = connection.CreateCommand();
96+
command.CommandText = @"CREATE TABLE IF NOT EXISTS votes (
97+
id VARCHAR(255) NOT NULL UNIQUE,
98+
vote VARCHAR(255) NOT NULL
99+
)";
100+
command.ExecuteNonQuery();
101+
102+
return connection;
103+
}
104+
105+
private static ConnectionMultiplexer OpenRedisConnection(string hostname)
106+
{
107+
// Use IP address to workaround https://github.com/StackExchange/StackExchange.Redis/issues/410
108+
var ipAddress = GetIp(hostname);
109+
Console.WriteLine($"Found redis at {ipAddress}");
110+
111+
while (true)
112+
{
113+
try
114+
{
115+
Console.Error.WriteLine("Connecting to redis");
116+
return ConnectionMultiplexer.Connect(ipAddress);
117+
}
118+
catch (RedisConnectionException)
119+
{
120+
Console.Error.WriteLine("Waiting for redis");
121+
Thread.Sleep(1000);
122+
}
123+
}
124+
}
125+
126+
private static string GetIp(string hostname)
127+
=> Dns.GetHostEntryAsync(hostname)
128+
.Result
129+
.AddressList
130+
.First(a => a.AddressFamily == AddressFamily.InterNetwork)
131+
.ToString();
132+
133+
private static void UpdateVote(NpgsqlConnection connection, string voterId, string vote)
134+
{
135+
var command = connection.CreateCommand();
136+
try
137+
{
138+
command.CommandText = "INSERT INTO votes (id, vote) VALUES (@id, @vote)";
139+
command.Parameters.AddWithValue("@id", voterId);
140+
command.Parameters.AddWithValue("@vote", vote);
141+
command.ExecuteNonQuery();
142+
}
143+
catch (DbException)
144+
{
145+
command.CommandText = "UPDATE votes SET vote = @vote WHERE id = @id";
146+
command.ExecuteNonQuery();
147+
}
148+
finally
149+
{
150+
command.Dispose();
151+
}
152+
}
153+
}
154+
}

src/Worker/Worker.csproj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
6+
<AssemblyName>Worker</AssemblyName>
7+
<OutputType>Exe</OutputType>
8+
<PackageId>Worker</PackageId>
9+
<ServerGarbageCollection>true</ServerGarbageCollection>
10+
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="StackExchange.Redis" Version="1.1.604-alpha" />
15+
<PackageReference Include="Npgsql" Version="3.1.3" />
16+
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
17+
</ItemGroup>
18+
19+
</Project>

0 commit comments

Comments
 (0)