mirror of
https://github.com/alanorth/cgspace-notes.git
synced 2024-11-05 06:43:00 +01:00
313 lines
19 KiB
HTML
313 lines
19 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
|
||
|
<head>
|
||
|
<meta charset="utf-8">
|
||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
|
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||
|
|
||
|
<meta property="og:title" content="December, 2016" />
|
||
|
<meta property="og:description" content="2016-12-02 CGSpace was down for five hours in the morning while I was sleeping While looking in the logs for errors, I see tons of warnings about Atmire MQM: 2016-12-02 03:00:32,352 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=CREATE, SubjectType=BUNDLE, SubjectID=70316, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632305, dispatcher=1544803905, detail=[null], transactionID="TX157907838689377964651674089851855413607") 2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org." />
|
||
|
<meta property="og:type" content="article" />
|
||
|
<meta property="og:url" content="https://alanorth.github.io/cgspace-notes/2016-12/" />
|
||
|
|
||
|
|
||
|
<meta property="og:updated_time" content="2016-12-02T10:43:00+03:00"/>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
<meta itemprop="name" content="December, 2016">
|
||
|
<meta itemprop="description" content="2016-12-02 CGSpace was down for five hours in the morning while I was sleeping While looking in the logs for errors, I see tons of warnings about Atmire MQM: 2016-12-02 03:00:32,352 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=CREATE, SubjectType=BUNDLE, SubjectID=70316, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632305, dispatcher=1544803905, detail=[null], transactionID="TX157907838689377964651674089851855413607") 2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.">
|
||
|
|
||
|
|
||
|
<meta itemprop="dateModified" content="2016-12-02T10:43:00+03:00" />
|
||
|
<meta itemprop="wordCount" content="520">
|
||
|
|
||
|
|
||
|
|
||
|
<meta itemprop="keywords" content="notes," />
|
||
|
|
||
|
|
||
|
|
||
|
<meta name="twitter:card" content="summary"/>
|
||
|
|
||
|
|
||
|
|
||
|
<meta name="twitter:title" content="December, 2016"/>
|
||
|
<meta name="twitter:description" content="2016-12-02 CGSpace was down for five hours in the morning while I was sleeping While looking in the logs for errors, I see tons of warnings about Atmire MQM: 2016-12-02 03:00:32,352 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=CREATE, SubjectType=BUNDLE, SubjectID=70316, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632305, dispatcher=1544803905, detail=[null], transactionID="TX157907838689377964651674089851855413607") 2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org."/>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
<meta name="generator" content="Hugo 0.17" />
|
||
|
|
||
|
|
||
|
<base href="https://alanorth.github.io/cgspace-notes/">
|
||
|
<link rel="canonical" href="https://alanorth.github.io/cgspace-notes/2016-12/">
|
||
|
|
||
|
<title>December, 2016 | CGSpace Notes</title>
|
||
|
|
||
|
<!-- combined, minified CSS -->
|
||
|
<link href="https://alanorth.github.io/cgspace-notes/css/style.css" rel="stylesheet">
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</head>
|
||
|
|
||
|
<body>
|
||
|
|
||
|
<div class="blog-masthead">
|
||
|
<div class="container">
|
||
|
<nav class="nav blog-nav">
|
||
|
<a class="nav-link " href="https://alanorth.github.io/cgspace-notes/">Home</a>
|
||
|
|
||
|
|
||
|
|
||
|
</nav>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<header class="blog-header">
|
||
|
<div class="container">
|
||
|
<h1 class="blog-title"><a href="https://alanorth.github.io/cgspace-notes/" rel="home">CGSpace Notes</a></h1>
|
||
|
|
||
|
</div>
|
||
|
</header>
|
||
|
|
||
|
<div class="container">
|
||
|
<div class="row">
|
||
|
<div class="col-sm-8 blog-main">
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
<article class="blog-post">
|
||
|
<header>
|
||
|
<h2 class="blog-post-title"><a href="https://alanorth.github.io/cgspace-notes/2016-12/">December, 2016</a></h2>
|
||
|
<p class="blog-post-meta"><time datetime="2016-12-02T10:43:00+03:00">Fri Dec 02, 2016</time> by Alan Orth in
|
||
|
|
||
|
<i class="fa fa-tag" aria-hidden="true"></i> <a href="/cgspace-notes/tags/notes" rel="tag">Notes</a>
|
||
|
|
||
|
</p>
|
||
|
</header>
|
||
|
|
||
|
|
||
|
<h2 id="2016-12-02">2016-12-02</h2>
|
||
|
|
||
|
<ul>
|
||
|
<li>CGSpace was down for five hours in the morning while I was sleeping</li>
|
||
|
<li>While looking in the logs for errors, I see tons of warnings about Atmire MQM:</li>
|
||
|
</ul>
|
||
|
|
||
|
<pre><code>2016-12-02 03:00:32,352 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=CREATE, SubjectType=BUNDLE, SubjectID=70316, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632305, dispatcher=1544803905, detail=[null], transactionID="TX157907838689377964651674089851855413607")
|
||
|
2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=MODIFY_METADATA, SubjectType=BUNDLE, SubjectID =70316, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632309, dispatcher=1544803905, detail="dc.title", transactionID="TX157907838689377964651674089851855413607")
|
||
|
2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=ADD, SubjectType=ITEM, SubjectID=80044, Object Type=BUNDLE, ObjectID=70316, TimeStamp=1480647632311, dispatcher=1544803905, detail="THUMBNAIL", transactionID="TX157907838689377964651674089851855413607")
|
||
|
2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=ADD, SubjectType=BUNDLE, SubjectID=70316, Obje ctType=BITSTREAM, ObjectID=86715, TimeStamp=1480647632318, dispatcher=1544803905, detail="-1", transactionID="TX157907838689377964651674089851855413607")
|
||
|
2016-12-02 03:00:32,353 WARN com.atmire.metadataquality.batchedit.BatchEditConsumer @ BatchEditConsumer should not have been given this kind of Subject in an event, skipping: org.dspace.event.Event(eventType=MODIFY, SubjectType=ITEM, SubjectID=80044, ObjectType=(Unknown), ObjectID=-1, TimeStamp=1480647632351, dispatcher=1544803905, detail=[null], transactionID="TX157907838689377964651674089851855413607")
|
||
|
</code></pre>
|
||
|
|
||
|
<ul>
|
||
|
<li>I see thousands of them in the logs for the last few months, so it’s not related to the DSpace 5.5 upgrade</li>
|
||
|
<li>I’ve raised a ticket with Atmire to ask</li>
|
||
|
<li>Another worrying error from dspace.log is:</li>
|
||
|
</ul>
|
||
|
|
||
|
<pre><code>org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoSuchMethodError: com.atmire.statistics.generator.DSpaceObjectDatasetGenerator.toDatasetQuery(Lorg/dspace/core/Context;)Lcom/atmire/statistics/content/DatasetQuery;
|
||
|
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:972)
|
||
|
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
|
||
|
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
|
||
|
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
|
||
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
|
||
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
|
||
|
at org.dspace.app.xmlui.cocoon.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:111)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
|
||
|
at org.dspace.app.xmlui.cocoon.DSpaceCocoonServletFilter.doFilter(DSpaceCocoonServletFilter.java:274)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
|
||
|
at org.dspace.app.xmlui.cocoon.servlet.multipart.DSpaceMultipartFilter.doFilter(DSpaceMultipartFilter.java:119)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
|
||
|
at org.dspace.utils.servlet.DSpaceWebappServletFilter.doFilter(DSpaceWebappServletFilter.java:78)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
|
||
|
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
|
||
|
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
|
||
|
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
|
||
|
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
|
||
|
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
|
||
|
at com.googlecode.psiprobe.Tomcat70AgentValve.invoke(Tomcat70AgentValve.java:44)
|
||
|
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
|
||
|
at org.apache.catalina.valves.CrawlerSessionManagerValve.invoke(CrawlerSessionManagerValve.java:180)
|
||
|
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
|
||
|
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
|
||
|
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
|
||
|
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
|
||
|
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
|
||
|
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
|
||
|
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
|
||
|
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
|
||
|
at java.lang.Thread.run(Thread.java:745)
|
||
|
Caused by: java.lang.NoSuchMethodError: com.atmire.statistics.generator.DSpaceObjectDatasetGenerator.toDatasetQuery(Lorg/dspace/core/Context;)Lcom/atmire/statistics/content/DatasetQuery;
|
||
|
at com.atmire.statistics.generator.TopNDSODatasetGenerator.toDatasetQuery(SourceFile:39)
|
||
|
at com.atmire.statistics.display.StatisticsDataVisitsMultidata.createDataset(SourceFile:108)
|
||
|
at org.dspace.statistics.content.StatisticsDisplay.createDataset(SourceFile:384)
|
||
|
at org.dspace.statistics.content.StatisticsDisplay.getDataset(SourceFile:404)
|
||
|
at com.atmire.statistics.mostpopular.JSONStatsMostPopularGenerator.generateJsonData(SourceFile:170)
|
||
|
at com.atmire.statistics.mostpopular.JSONStatsMostPopularGenerator.generate(SourceFile:246)
|
||
|
at com.atmire.app.xmlui.aspect.statistics.JSONStatsMostPopular.generate(JSONStatsMostPopular.java:145)
|
||
|
at sun.reflect.GeneratedMethodAccessor296.invoke(Unknown Source)
|
||
|
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||
|
at java.lang.reflect.Method.invoke(Method.java:498)
|
||
|
at org.apache.cocoon.core.container.spring.avalon.PoolableProxyHandler.invoke(PoolableProxyHandler.java:71)
|
||
|
at com.sun.proxy.$Proxy96.process(Unknown Source)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.ReadNode.invoke(ReadNode.java:94)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:55)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.MatchNode.invoke(MatchNode.java:87)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:55)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.MatchNode.invoke(MatchNode.java:87)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:78)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.PipelineNode.invoke(PipelineNode.java:143)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:78)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode.invoke(PipelinesNode.java:81)
|
||
|
at org.apache.cocoon.components.treeprocessor.ConcreteTreeProcessor.process(ConcreteTreeProcessor.java:239)
|
||
|
at org.apache.cocoon.components.treeprocessor.ConcreteTreeProcessor.process(ConcreteTreeProcessor.java:171)
|
||
|
at org.apache.cocoon.components.treeprocessor.TreeProcessor.process(TreeProcessor.java:247)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.MountNode.invoke(MountNode.java:117)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:55)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.MatchNode.invoke(MatchNode.java:87)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:78)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.PipelineNode.invoke(PipelineNode.java:143)
|
||
|
at org.apache.cocoon.components.treeprocessor.AbstractParentProcessingNode.invokeNodes(AbstractParentProcessingNode.java:78)
|
||
|
at org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode.invoke(PipelinesNode.java:81)
|
||
|
at org.apache.cocoon.components.treeprocessor.ConcreteTreeProcessor.process(ConcreteTreeProcessor.java:239)
|
||
|
at org.apache.cocoon.components.treeprocessor.ConcreteTreeProcessor.process(ConcreteTreeProcessor.java:171)
|
||
|
at org.apache.cocoon.components.treeprocessor.TreeProcessor.process(TreeProcessor.java:247)
|
||
|
at org.apache.cocoon.servlet.RequestProcessor.process(RequestProcessor.java:351)
|
||
|
at org.apache.cocoon.servlet.RequestProcessor.service(RequestProcessor.java:169)
|
||
|
at org.apache.cocoon.sitemap.SitemapServlet.service(SitemapServlet.java:84)
|
||
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
|
||
|
at org.apache.cocoon.servletservice.ServletServiceContext$PathDispatcher.forward(ServletServiceContext.java:468)
|
||
|
at org.apache.cocoon.servletservice.ServletServiceContext$PathDispatcher.forward(ServletServiceContext.java:443)
|
||
|
at org.apache.cocoon.servletservice.spring.ServletFactoryBean$ServiceInterceptor.invoke(ServletFactoryBean.java:264)
|
||
|
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
|
||
|
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
|
||
|
at com.sun.proxy.$Proxy89.service(Unknown Source)
|
||
|
at org.dspace.springmvc.CocoonView.render(CocoonView.java:113)
|
||
|
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1180)
|
||
|
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:950)
|
||
|
... 35 more
|
||
|
</code></pre>
|
||
|
|
||
|
<ul>
|
||
|
<li>The first error I see in dspace.log this morning is:</li>
|
||
|
</ul>
|
||
|
|
||
|
<pre><code>2016-12-02 03:00:46,656 ERROR org.dspace.authority.AuthorityValueFinder @ anonymous::Error while retrieving AuthorityValue from solr:query\colon; id\colon;"b0b541c1-ec15-48bf-9209-6dbe8e338cdc"
|
||
|
org.apache.solr.client.solrj.SolrServerException: Server refused connection at: http://localhost:8081/solr/authority
|
||
|
</code></pre>
|
||
|
|
||
|
<ul>
|
||
|
<li>Looking through DSpace’s solr log I see that about 20 seconds before this, there were a few 30+ KiB solr queries</li>
|
||
|
<li>The last logs here right before Solr became unresponsive (and right after I restarted it five hours later) were:</li>
|
||
|
</ul>
|
||
|
|
||
|
<pre><code>2016-12-02 03:00:42,606 INFO org.apache.solr.core.SolrCore @ [statistics] webapp=/solr path=/select params={q=containerItem:72828+AND+type:0&shards=localhost:8081/solr/statistics-2010,localhost:8081/solr/statistics&fq=-isInternal:true&fq=-(author_mtdt:"CGIAR\+Institutional\+Learning\+and\+Change\+Initiative"++AND+subject_mtdt:"PARTNERSHIPS"+AND+subject_mtdt:"RESEARCH"+AND+subject_mtdt:"AGRICULTURE"+AND+subject_mtdt:"DEVELOPMENT"++AND+iso_mtdt:"en"+)&rows=0&wt=javabin&version=2} hits=0 status=0 QTime=19
|
||
|
2016-12-02 08:28:23,908 INFO org.apache.solr.servlet.SolrDispatchFilter @ SolrDispatchFilter.init()
|
||
|
</code></pre>
|
||
|
|
||
|
<ul>
|
||
|
<li>DSpace’s own Solr logs don’t give IP addresses, so I will have to enable Nginx’s logging of <code>/solr</code> so I can see where this request came from</li>
|
||
|
<li>I enabled logging of <code>/rest/</code> and I think I’ll leave it on for good</li>
|
||
|
</ul>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</article>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
</div> <!-- /.blog-main -->
|
||
|
|
||
|
<aside class="col-sm-3 offset-sm-1 blog-sidebar">
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
<section class="sidebar-module">
|
||
|
<h4>Recent Posts</h4>
|
||
|
<ol class="list-unstyled">
|
||
|
|
||
|
<li><a href="/cgspace-notes/2016-12/">December, 2016</a></li>
|
||
|
|
||
|
<li><a href="/cgspace-notes/2016-11/">November, 2016</a></li>
|
||
|
|
||
|
<li><a href="/cgspace-notes/2016-10/">October, 2016</a></li>
|
||
|
|
||
|
<li><a href="/cgspace-notes/2016-09/">September, 2016</a></li>
|
||
|
|
||
|
<li><a href="/cgspace-notes/2016-08/">August, 2016</a></li>
|
||
|
|
||
|
</ol>
|
||
|
</section>
|
||
|
|
||
|
|
||
|
<section class="sidebar-module">
|
||
|
<h4>Links</h4>
|
||
|
<ol class="list-unstyled">
|
||
|
|
||
|
<li><a href="https://cgspace.cgiar.org">CGSpace</a></li>
|
||
|
|
||
|
<li><a href="https://dspacetest.cgiar.org">DSpace Test</a></li>
|
||
|
|
||
|
<li><a href="https://github.com/ilri/DSpace">CGSpace @ GitHub</a></li>
|
||
|
|
||
|
</ol>
|
||
|
</section>
|
||
|
|
||
|
</aside>
|
||
|
|
||
|
|
||
|
</div> <!-- /.row -->
|
||
|
</div> <!-- /.container -->
|
||
|
|
||
|
<footer class="blog-footer">
|
||
|
<p>
|
||
|
|
||
|
Blog template created by <a href="https://twitter.com/mdo">@mdo</a>, ported to Hugo by <a href='https://twitter.com/mralanorth'>@mralanorth</a>.
|
||
|
|
||
|
</p>
|
||
|
<p>
|
||
|
<a href="https://alanorth.github.io/cgspace-notes/2016-12/#">Back to top</a>
|
||
|
</p>
|
||
|
</footer>
|
||
|
|
||
|
</body>
|
||
|
|
||
|
</html>
|