foreign_actor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ Gemfile.lock
5
+ coverage/
6
+ doc/
7
+ tests/
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'celluloid', git: 'git://github.com/celluloid/celluloid.git'
7
+
8
+ group(:test) do
9
+ gem 'eetee', '= 0.0.1'
10
+ gem 'mocha', '~> 0.12.0'
11
+ gem 'factory_girl'
12
+
13
+ gem 'simplecov'
14
+ gem 'guard', '~> 1.5.4'
15
+ gem 'rb-fsevent'
16
+ gem 'growl'
17
+ end
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+
2
+ # parameters:
3
+ # output => the formatted to use
4
+ # backtrace => number of lines, nil = everything
5
+ guard 'bacon', :output => "BetterOutput", :backtrace => nil do
6
+ watch(%r{^lib/foreign_actor/(.+)\.rb$}) { |m| "specs/unit/#{m[1]}_spec.rb" }
7
+ watch(%r{specs/.+\.rb$})
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Julien Ammous
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # What is this ?
2
+
3
+ I wanted to combine two great libraries for concurrent work: Crossroads (zeromq fork) and Celluloid.
4
+ Foreign Actor is my attempt at building a simple way to distribute celluloid actors
5
+ in multiple processes/machines, the idea is to be able to move an actor to another process with no change to the actor itself and minimal changes on the server and client side code.
6
+
7
+
8
+ # How ?
9
+ I used the Crossroads library (zeromq fork) to achieve my goal using XREQ/XREP sockets, this allows multiple clients with multiple workers, each clients being able to make calls with
10
+ or without a return value.
11
+
12
+ Here is what it looks like:
13
+ ![](docs/design.png "Architecture")
14
+
15
+ In this picture the square boxes are processes while the rounded box are the services (actors).
16
+ In a standard client/server interaction one client connects to one or more server and to do so
17
+ needs to know the addresses of all of them, in my case the device process is here to simplify this: both clients and workers connects to it which allows as a direct result things like adding
18
+ a worker in live, you just start it, it connects to the device and it can starts to process requests immediately.
19
+ Something to note is that each of these processes can run on a different physical machine
20
+ allowing effectively to distribute jobs on multiple cores as well as multiple machines.
21
+
22
+
23
+ # Constraints
24
+ Since the arguments to the actions are serialized using MessagePack they need to be serializable by it, which means that you can only send basic types to a foreign actor but messagepack allows everything you need really:
25
+ - Integers
26
+ - Floats (ex: 2.34)
27
+ - Strings (ex: "a cat")
28
+ - Hash
29
+ - Boolean
30
+ - Nil
31
+ - Array (ex: [1, "a cat", {"a" : nil}])
32
+
33
+ Why not going with Marshal ? Because I don't want to close the door to another language, you
34
+ can well imagine calling a service which is in fact provided bya C server, a python server or
35
+ anything else, I just see no reason to close that door.
36
+
37
+
38
+ # Examples
39
+ You can look at the example and example2 folder.
40
+
41
+
42
+ # TODO
43
+ - handle workers crash better, the client should be informed instead of waiting for a timeout.
44
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require "bundler/gem_tasks"
4
+
5
+ task :default => :test
6
+
7
+ task :test do
8
+
9
+ # do not generate coverage report under travis
10
+ # unless ENV['TRAVIS']
11
+
12
+ # require 'simplecov'
13
+ # SimpleCov.command_name "E.T."
14
+ # SimpleCov.start do
15
+ # add_filter ".*_spec"
16
+ # add_filter "/helpers/"
17
+ # end
18
+ # end
19
+
20
+ require 'eetee'
21
+
22
+ runner = EEtee::Runner.new
23
+ runner.run_pattern('specs/**/*_spec.rb')
24
+ runner.report_results()
25
+
26
+ end
27
+
data/bin/device ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ffi-rxs'
4
+ require 'yaml'
5
+
6
+ context = XS::Context.new
7
+ poller = XS::Poller.new
8
+
9
+ path = nil
10
+
11
+ if ARGV.size == 0
12
+ path = 'device.yml'
13
+ elsif ARGV
14
+ path = ARGV[0]
15
+ end
16
+
17
+ unless path && File.exists?(path)
18
+ puts "Usage: #{File.basename($0)} <config_path>"
19
+ exit(1)
20
+ end
21
+
22
+ puts "Using #{path}"
23
+
24
+ conf = YAML.load_file(path)
25
+ endpoints = []
26
+
27
+ Pipe = Struct.new(:front, :back) do
28
+
29
+ end
30
+
31
+ conf.each do |name, paths|
32
+ frontend = context.socket(XS::XREP)
33
+ backend = context.socket(XS::XREQ)
34
+
35
+ frontend.bind(paths['front'])
36
+ backend.bind(paths['back'])
37
+
38
+ poller.register_readable(frontend)
39
+ poller.register_readable(backend)
40
+
41
+
42
+ endpoints << Pipe.new(frontend, backend)
43
+ end
44
+
45
+
46
+ trap('INT'){ exit }
47
+ trap('TERM'){ exit }
48
+
49
+ loop do
50
+ poller.poll()
51
+ poller.readables.each do |socket|
52
+ endpoints.each do |p|
53
+ if socket === p.front
54
+ loop do
55
+ socket.recv_string(message = "")
56
+ more = socket.more_parts?
57
+ p.back.send_string(message, more ? XS::SNDMORE : 0)
58
+ break unless more
59
+ end
60
+ next
61
+
62
+ elsif socket === p.back
63
+ loop do
64
+ socket.recv_string(message = "")
65
+ more = socket.more_parts?
66
+ p.front.send_string(message, more ? XS::SNDMORE : 0)
67
+ break unless more
68
+ end
69
+ next
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+
@@ -0,0 +1,100 @@
1
+
2
+ require 'ffi-rxs'
3
+
4
+ class PuppetMaster
5
+ def initialize(workers_endpoint, client_endpoint)
6
+ @context = XS::Context.new
7
+ @source_id = nil
8
+
9
+ @workers_socket = @context.socket(XS::XREQ)
10
+ @clients_socket = @context.socket(XS::XREP)
11
+
12
+ @workers_socket.bind(workers_endpoint)
13
+ @clients_socket.bind(client_endpoint)
14
+
15
+ @control_socket_srv = @context.socket(XS::PAIR)
16
+ @control_socket_cl = @context.socket(XS::PAIR)
17
+
18
+ addr = "inproc://ctrl"
19
+ @control_socket_srv.bind(addr)
20
+ @control_socket_cl.connect(addr)
21
+ end
22
+
23
+
24
+ def start()
25
+ trap('INT'){ @control_socket_cl.send_string('') }
26
+ trap('TERM'){ @control_socket_cl.send_string('') }
27
+
28
+ poller = XS::Poller.new
29
+ poller.register_readable(@workers_socket)
30
+ poller.register_readable(@clients_socket)
31
+ # control socket
32
+ poller.register_readable(@control_socket_srv)
33
+
34
+ exit = false
35
+
36
+ while exit == false
37
+ poller.poll
38
+ poller.readables.each do |s|
39
+ if s == @control_socket_srv
40
+ puts "Exiting..."
41
+ exit = true
42
+ else
43
+ redirect_data(s)
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+
51
+ def redirect_data(from)
52
+ # read whole message
53
+ msg = receive_msg(from)
54
+ if msg
55
+ # store the source_id
56
+ if from == @clients_socket
57
+ @source_id = msg.shift
58
+
59
+ # send to the other side
60
+ handle_xs_err(@workers_socket, :send_string, msg[0], XS::DONTWAIT)
61
+
62
+ else
63
+ handle_xs_err(@clients_socket, :send_string, @source_id, XS::DONTWAIT | XS::SNDMORE)
64
+ handle_xs_err(@clients_socket, :send_string, msg[0], XS::DONTWAIT)
65
+
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ private
72
+ def receive_msg(s)
73
+ parts = []
74
+ loop do
75
+ data = ""
76
+ if s.recv_string(data, XS::DONTWAIT) == -1
77
+ raise "error while reading socket: #{}"
78
+ end
79
+ parts << data
80
+ break unless s.more_parts?
81
+ end
82
+
83
+ parts
84
+
85
+ rescue
86
+ nil
87
+ end
88
+
89
+ def handle_xs_err(s, method, *args)
90
+ rc = s.send(method, *args)
91
+ unless XS::Util.resultcode_ok?(rc)
92
+ raise "error: #{method}: #{XS::Util.error_string}"
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ puts "Started."
99
+ PuppetMaster.new('tcp://127.0.0.1:7001', 'tcp://127.0.0.1:7000').start
100
+
@@ -0,0 +1,221 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
3
+ <!--Created by yFiles for Java 2.8-->
4
+ <key for="graphml" id="d0" yfiles.type="resources"/>
5
+ <key for="port" id="d1" yfiles.type="portgraphics"/>
6
+ <key for="port" id="d2" yfiles.type="portgeometry"/>
7
+ <key for="port" id="d3" yfiles.type="portuserdata"/>
8
+ <key attr.name="url" attr.type="string" for="node" id="d4"/>
9
+ <key attr.name="description" attr.type="string" for="node" id="d5"/>
10
+ <key for="node" id="d6" yfiles.type="nodegraphics"/>
11
+ <key attr.name="Description" attr.type="string" for="graph" id="d7"/>
12
+ <key attr.name="url" attr.type="string" for="edge" id="d8"/>
13
+ <key attr.name="description" attr.type="string" for="edge" id="d9"/>
14
+ <key for="edge" id="d10" yfiles.type="edgegraphics"/>
15
+ <graph edgedefault="directed" id="G">
16
+ <data key="d7"/>
17
+ <node id="n0">
18
+ <data key="d5"/>
19
+ <data key="d6">
20
+ <y:ShapeNode>
21
+ <y:Geometry height="26.0" width="92.0" x="590.0" y="489.0"/>
22
+ <y:Fill hasColor="false" transparent="false"/>
23
+ <y:BorderStyle color="#000000" type="line" width="1.0"/>
24
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="21.853515625" x="35.0732421875" y="3.93359375">W1</y:NodeLabel>
25
+ <y:Shape type="roundrectangle"/>
26
+ </y:ShapeNode>
27
+ </data>
28
+ </node>
29
+ <node id="n1">
30
+ <data key="d5"/>
31
+ <data key="d6">
32
+ <y:ShapeNode>
33
+ <y:Geometry height="120.0" width="122.0" x="579.0" y="477.0"/>
34
+ <y:Fill hasColor="false" transparent="false"/>
35
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
36
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="sides" modelPosition="s" textColor="#000000" visible="true" width="45.15625" x="38.421875" y="124.0">Worker</y:NodeLabel>
37
+ <y:Shape type="rectangle"/>
38
+ </y:ShapeNode>
39
+ </data>
40
+ </node>
41
+ <node id="n2">
42
+ <data key="d5"/>
43
+ <data key="d6">
44
+ <y:ShapeNode>
45
+ <y:Geometry height="41.0" width="109.0" x="495.0" y="390.0"/>
46
+ <y:Fill hasColor="false" transparent="false"/>
47
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
48
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="42.185546875" x="33.4072265625" y="11.43359375">Device</y:NodeLabel>
49
+ <y:Shape type="rectangle"/>
50
+ </y:ShapeNode>
51
+ </data>
52
+ </node>
53
+ <node id="n3">
54
+ <data key="d5"/>
55
+ <data key="d6">
56
+ <y:ShapeNode>
57
+ <y:Geometry height="60.0" width="74.0" x="327.0" y="240.0"/>
58
+ <y:Fill hasColor="false" transparent="false"/>
59
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
60
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="37.861328125" x="18.0693359375" y="20.93359375">Client</y:NodeLabel>
61
+ <y:Shape type="rectangle"/>
62
+ </y:ShapeNode>
63
+ </data>
64
+ </node>
65
+ <node id="n4">
66
+ <data key="d5"/>
67
+ <data key="d6">
68
+ <y:ShapeNode>
69
+ <y:Geometry height="60.0" width="74.0" x="452.0" y="240.0"/>
70
+ <y:Fill hasColor="false" transparent="false"/>
71
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
72
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="37.861328125" x="18.0693359375" y="20.93359375">Client</y:NodeLabel>
73
+ <y:Shape type="rectangle"/>
74
+ </y:ShapeNode>
75
+ </data>
76
+ </node>
77
+ <node id="n5">
78
+ <data key="d5"/>
79
+ <data key="d6">
80
+ <y:ShapeNode>
81
+ <y:Geometry height="60.0" width="74.0" x="577.0" y="240.0"/>
82
+ <y:Fill hasColor="false" transparent="false"/>
83
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
84
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="37.861328125" x="18.0693359375" y="20.93359375">Client</y:NodeLabel>
85
+ <y:Shape type="rectangle"/>
86
+ </y:ShapeNode>
87
+ </data>
88
+ </node>
89
+ <node id="n6">
90
+ <data key="d5"/>
91
+ <data key="d6">
92
+ <y:ShapeNode>
93
+ <y:Geometry height="60.0" width="74.0" x="702.0" y="240.0"/>
94
+ <y:Fill hasColor="false" transparent="false"/>
95
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
96
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="37.861328125" x="18.0693359375" y="20.93359375">Client</y:NodeLabel>
97
+ <y:Shape type="rectangle"/>
98
+ </y:ShapeNode>
99
+ </data>
100
+ </node>
101
+ <node id="n7">
102
+ <data key="d5"/>
103
+ <data key="d6">
104
+ <y:ShapeNode>
105
+ <y:Geometry height="26.0" width="92.0" x="416.0" y="529.0"/>
106
+ <y:Fill hasColor="false" transparent="false"/>
107
+ <y:BorderStyle color="#000000" type="line" width="1.0"/>
108
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="21.853515625" x="35.0732421875" y="3.93359375">W2</y:NodeLabel>
109
+ <y:Shape type="roundrectangle"/>
110
+ </y:ShapeNode>
111
+ </data>
112
+ </node>
113
+ <node id="n8">
114
+ <data key="d5"/>
115
+ <data key="d6">
116
+ <y:ShapeNode>
117
+ <y:Geometry height="26.0" width="92.0" x="416.0" y="489.0"/>
118
+ <y:Fill hasColor="false" transparent="false"/>
119
+ <y:BorderStyle color="#000000" type="line" width="1.0"/>
120
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="internal" modelPosition="c" textColor="#000000" visible="true" width="21.853515625" x="35.0732421875" y="3.93359375">W1</y:NodeLabel>
121
+ <y:Shape type="roundrectangle"/>
122
+ </y:ShapeNode>
123
+ </data>
124
+ </node>
125
+ <node id="n9">
126
+ <data key="d5"/>
127
+ <data key="d6">
128
+ <y:ShapeNode>
129
+ <y:Geometry height="120.0" width="122.0" x="405.0" y="477.0"/>
130
+ <y:Fill hasColor="false" transparent="false"/>
131
+ <y:BorderStyle color="#000000" type="line" width="2.0"/>
132
+ <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="sides" modelPosition="s" textColor="#000000" visible="true" width="45.15625" x="38.421875" y="124.0">Worker</y:NodeLabel>
133
+ <y:Shape type="rectangle"/>
134
+ </y:ShapeNode>
135
+ </data>
136
+ </node>
137
+ <edge id="e0" source="n0" target="n1">
138
+ <data key="d10">
139
+ <y:PolyLineEdge>
140
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
141
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
142
+ <y:Arrows source="none" target="standard"/>
143
+ <y:BendStyle smoothed="false"/>
144
+ </y:PolyLineEdge>
145
+ </data>
146
+ </edge>
147
+ <edge id="e1" source="n8" target="n9">
148
+ <data key="d10">
149
+ <y:PolyLineEdge>
150
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
151
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
152
+ <y:Arrows source="none" target="standard"/>
153
+ <y:BendStyle smoothed="false"/>
154
+ </y:PolyLineEdge>
155
+ </data>
156
+ </edge>
157
+ <edge id="e2" source="n3" target="n2">
158
+ <data key="d10">
159
+ <y:PolyLineEdge>
160
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
161
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
162
+ <y:Arrows source="delta" target="delta"/>
163
+ <y:BendStyle smoothed="false"/>
164
+ </y:PolyLineEdge>
165
+ </data>
166
+ </edge>
167
+ <edge id="e3" source="n4" target="n2">
168
+ <data key="d10">
169
+ <y:PolyLineEdge>
170
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
171
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
172
+ <y:Arrows source="delta" target="delta"/>
173
+ <y:BendStyle smoothed="false"/>
174
+ </y:PolyLineEdge>
175
+ </data>
176
+ </edge>
177
+ <edge id="e4" source="n5" target="n2">
178
+ <data key="d10">
179
+ <y:PolyLineEdge>
180
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
181
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
182
+ <y:Arrows source="delta" target="delta"/>
183
+ <y:BendStyle smoothed="false"/>
184
+ </y:PolyLineEdge>
185
+ </data>
186
+ </edge>
187
+ <edge id="e5" source="n6" target="n2">
188
+ <data key="d10">
189
+ <y:PolyLineEdge>
190
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
191
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
192
+ <y:Arrows source="delta" target="delta"/>
193
+ <y:BendStyle smoothed="false"/>
194
+ </y:PolyLineEdge>
195
+ </data>
196
+ </edge>
197
+ <edge id="e6" source="n2" target="n9">
198
+ <data key="d10">
199
+ <y:PolyLineEdge>
200
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
201
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
202
+ <y:Arrows source="delta" target="delta"/>
203
+ <y:BendStyle smoothed="false"/>
204
+ </y:PolyLineEdge>
205
+ </data>
206
+ </edge>
207
+ <edge id="e7" source="n2" target="n1">
208
+ <data key="d10">
209
+ <y:PolyLineEdge>
210
+ <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
211
+ <y:LineStyle color="#000000" type="line" width="1.0"/>
212
+ <y:Arrows source="delta" target="delta"/>
213
+ <y:BendStyle smoothed="false"/>
214
+ </y:PolyLineEdge>
215
+ </data>
216
+ </edge>
217
+ </graph>
218
+ <data key="d0">
219
+ <y:Resources/>
220
+ </data>
221
+ </graphml>