From 700ab905f0d4aed7d65f040dc1cbff9ee06f34aa Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 16 Aug 2017 11:47:10 +0300 Subject: [PATCH] Use Spymemcached Transcoder as parent of Codec * Use codecs from alexandru's branch. For generic case, bring back old one because it works with big instances * Fix and update FakeMemcached --- .travis.yml | 38 ++- README.md | 4 +- build.sbt | 99 ++++-- project/.gnupg/pubring.gpg | Bin 0 -> 72123 bytes project/.gnupg/secring.gpg | Bin 0 -> 4875 bytes project/plugins.sbt | 12 +- project/publish | 43 +++ release-notes/1.10.md | 7 + src/main/scala/shade/memcached/Codec.scala | 322 ++++++++++++------ .../scala/shade/memcached/FakeMemcached.scala | 53 ++- .../scala/shade/memcached/MemcachedImpl.scala | 14 +- .../internals/SpyMemcachedIntegration.scala | 28 +- .../{tests => memcached}/CodecsSuite.scala | 17 +- .../shade/tests/MemcachedTestHelpers.scala | 2 +- 14 files changed, 410 insertions(+), 229 deletions(-) create mode 100644 project/.gnupg/pubring.gpg create mode 100644 project/.gnupg/secring.gpg create mode 100755 project/publish create mode 100644 release-notes/1.10.md rename src/test/scala/shade/{tests => memcached}/CodecsSuite.scala (77%) diff --git a/.travis.yml b/.travis.yml index 3be7b21..c308f00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,39 @@ language: scala sudo: required dist: trusty -scala: - - 2.10.6 - - 2.11.11 - - 2.12.2 -jdk: - - oraclejdk7 - - oraclejdk8 +group: edge + matrix: - exclude: - - scala: 2.12.2 - jdk: oraclejdk7 + include: + - jdk: oraclejdk8 + scala: 2.10.6 + env: COMMAND=ci PUBLISH= + - jdk: oraclejdk8 + scala: 2.11.11 + env: COMMAND=ci PUBLISH= + - jdk: oraclejdk8 + scala: 2.12.3 + env: COMMAND=ci PUBLISH=true + +env: + global: + - secure: GRdfKNrJn/zqjaDWE+16HCfuCSf/wsDpLHocxrOSDiW6QCy73a+MYCujfB989YndQkrmGVkzdmAyKhcfTyYW/Sqjh/sJc2OOc6p+4CeMOGRcLV73wTwi9PjsrzzN0260HnICq3X+3ZUiLdkWoJPLfD6Mflj9iRjJBQIOtV0LzeU= + - secure: SPSIblLKFVns7pVY1x3SEs4/16htY5HUzRC51uWXeESE7Nwi3SvBY8LE2BqHygQl/9wKKOdOKoCIBoftukWupIi/r1rT2nVFHremO23Y36hcffN+PFXtW6NIohwIoX34O6G7VGuS2b71IZQHqwr88bY4aHeU4jI3MtU3nXhbEMI= + - secure: YVx2BSSsqF7LdYTwinf6o8nqJiYL9FeFAm1HDLxt+ltuMAEbFprOEDA763FANZoUino0uYtOBQ9jWqgMsoo+DvWFrBk4eExC9jGRk7Y/aWw6lx+TCbISGYztkhREQf73JKjbejoxLXf9h9gfo3MpPdrQhzMd2zVKOgSNf8FddZA= + +script: + - sbt -J-Xmx6144m ++$TRAVIS_SCALA_VERSION $COMMAND +after_success: + - ./project/publish + services: - docker + before_install: - sudo service memcached stop - docker pull memcached - docker run -d -p 127.0.0.1:11211:11211 memcached memcached -script: "sbt clean coverage test" -after_success: "sbt coverageReport coveralls" + cache: directories: - $HOME/.sbt/0.13 diff --git a/README.md b/README.md index edfe6d6..1390849 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Shade - Memcached Client for Scala [![Build Status](https://travis-ci.org/monix/shade.svg?branch=master)](https://travis-ci.org/monix/shade) -[![Coverage Status](https://coveralls.io/repos/monix/shade/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexandru/shade?branch=master) [![Join the chat at https://gitter.im/monix/shade](https://badges.gitter.im/monix/shade.svg)](https://gitter.im/monix/shade?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Overview @@ -23,6 +22,7 @@ Supported for Scala versions: 2.10, 2.11 and 2.12. ## Release Notes +- [Version 1.10.x](release-notes/1.10.md) - [Version 1.9.x](release-notes/1.9.md) - [Version 1.8.x](release-notes/1.8.md) - [Version 1.7.x](release-notes/1.7.md) @@ -38,7 +38,7 @@ These are the people maintaining this project that you can annoy: ## Usage From SBT ```scala -dependencies += "io.monix" %% "shade" % "1.9.5" +dependencies += "io.monix" %% "shade" % "1.10.0" ``` ### Initializing the Memcached Client diff --git a/build.sbt b/build.sbt index bfd1edf..2a30555 100644 --- a/build.sbt +++ b/build.sbt @@ -1,13 +1,11 @@ name := "shade" - -version := "1.9.5" - organization := "io.monix" -scalaVersion := "2.11.11" - -crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2") +addCommandAlias("ci", ";clean ;compile ;test ;package") +addCommandAlias("release", ";+publishSigned ;sonatypeReleaseAll") +scalaVersion := "2.11.11" +crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.3") compileOrder in ThisBuild := CompileOrder.JavaThenScala scalacOptions ++= { @@ -56,9 +54,7 @@ scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match { scalacOptions in (Compile, doc) ~= (_ filterNot (_ == "-Xfatal-warnings")) resolvers ++= Seq( - "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases", - "Spy" at "http://files.couchbase.com/maven2/", - Resolver.sonatypeRepo("snapshots") + "Spy" at "http://files.couchbase.com/maven2/" ) libraryDependencies ++= Seq( @@ -72,47 +68,76 @@ libraryDependencies ++= Seq( libraryDependencies += ("org.scala-lang" % "scala-reflect" % scalaVersion.value % "compile") -// -- Settings meant for deployment on oss.sonatype.org +//------------- For Release -useGpg := true -useGpgAgent := true +useGpg := false usePgpKeyHex("2673B174C4071B0E") +pgpPublicRing := baseDirectory.value / "project" / ".gnupg" / "pubring.gpg" +pgpSecretRing := baseDirectory.value / "project" / ".gnupg" / "secring.gpg" +pgpPassphrase := sys.env.get("PGP_PASS").map(_.toArray) + +enablePlugins(GitVersioning) + +/* The BaseVersion setting represents the in-development (upcoming) version, + * as an alternative to SNAPSHOTS. + */ +git.baseVersion := "1.11.0" + +val ReleaseTag = """^v([\d\.]+)$""".r +git.gitTagToVersionNumber := { + case ReleaseTag(v) => Some(v) + case _ => None +} + +git.formattedShaVersion := { + val suffix = git.makeUncommittedSignifierSuffix(git.gitUncommittedChanges.value, git.uncommittedSignifier.value) + + git.gitHeadCommit.value map { _.substring(0, 7) } map { sha => + git.baseVersion.value + "-" + sha + suffix + } +} + +sonatypeProfileName := organization.value + +credentials += Credentials( + "Sonatype Nexus Repository Manager", + "oss.sonatype.org", + sys.env.getOrElse("SONATYPE_USER", ""), + sys.env.getOrElse("SONATYPE_PASS", "") +) publishMavenStyle := true -publishTo := { - val nexus = "https://oss.sonatype.org/" +isSnapshot := version.value endsWith "SNAPSHOT" + +publishTo := Some( if (isSnapshot.value) - Some("snapshots" at nexus + "content/repositories/snapshots") + Opts.resolver.sonatypeSnapshots else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} + Opts.resolver.sonatypeStaging +) publishArtifact in Test := false pomIncludeRepository := { _ => false } // removes optional dependencies scalariformSettings -pomExtra in ThisBuild := - https://github.com/monix/shade - - - The MIT License - http://opensource.org/licenses/MIT - repo - - - - git@github.com:monix/shade.git - scm:git:git@github.com:monix/shade.git - - - - alex_ndc - Alexandru Nedelcu - https://alexn.org - - +licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")) +homepage := Some(url("https://github.com/monix/shade")) + +scmInfo := Some( + ScmInfo( + url("https://github.com/monix/shade.git"), + "scm:git@github.com:monix/shade.git" + )) + +developers := List( + Developer( + id="alexelcu", + name="Alexandru Nedelcu", + email="noreply@alexn.org", + url=url("https://alexn.org") + )) // Multi-project-related diff --git a/project/.gnupg/pubring.gpg b/project/.gnupg/pubring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..a7a537eeed931716887da9ea96f9c71f35eaab34 GIT binary patch literal 72123 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%0Am8zp(ooO5!@D20B||I=(7U=?9)dK@-UvY6Gqr~ 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 VPn;j@aQz>#m6a!ua7VBL00Zf}2~+?8 literal 0 HcmV?d00001 diff --git a/project/.gnupg/secring.gpg b/project/.gnupg/secring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..3a7a327b8c3687e5b84645e1ff7782532b731cfb GIT binary patch literal 4875 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 literal 0 HcmV?d00001 diff --git a/project/plugins.sbt b/project/plugins.sbt index 939dc20..a29f8ca 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,11 +1,7 @@ resolvers += Classpaths.sbtPluginReleases -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") - +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.1") addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0") - -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0") - -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.1.0") - -addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.18") \ No newline at end of file +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.18") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3") diff --git a/project/publish b/project/publish new file mode 100755 index 0000000..2f196c8 --- /dev/null +++ b/project/publish @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby + +def exec(cmd) + abort("Error encountered, aborting") unless system(cmd) +end + +puts "CI=#{ENV['CI']}" +puts "TRAVIS_BRANCH=#{ENV['TRAVIS_BRANCH']}" +puts "TRAVIS_PULL_REQUEST=#{ENV['TRAVIS_PULL_REQUEST']}" +puts "PUBLISH=#{ENV['PUBLISH']}" +puts "SONATYPE_USER=xxxx" if ENV['SONATYPE_USER'] +puts "SONATYPE_PASS=xxxx" if ENV['SONATYPE_PASS'] +puts "PGP_PASS=xxxx" if ENV['PGP_PASS'] +puts + +unless ENV['CI'] == 'true' + abort("ERROR: Not running on top of Travis, aborting!") +end + +unless ENV['PUBLISH'] == 'true' + puts "Publish is disabled" + exit +end + +branch = ENV['TRAVIS_BRANCH'] +version = nil + +unless branch =~ /^v(\d+\.\d+\.\d+)$/ || + (branch == "snapshot" && ENV['TRAVIS_PULL_REQUEST'] == 'false') + + puts "Only deploying docs on the `publish` branch, or for version tags " + + "and not for pull requests or other branches, exiting!" + exit 0 +else + version = $1 + puts "Version branch detected: #{version}" if version +end + +# Forcing a change to the root directory, if not there already +Dir.chdir(File.absolute_path(File.join(File.dirname(__FILE__), ".."))) + +# Go, go, go +exec("sbt release") diff --git a/release-notes/1.10.md b/release-notes/1.10.md new file mode 100644 index 0000000..7741666 --- /dev/null +++ b/release-notes/1.10.md @@ -0,0 +1,7 @@ +# Version 1.10.0 - Aug 16, 2017 + +- Update SBT to 0.13.15 +- Update Scala versions +- Update SpyMemcached +- Add ability to configure the `timeoutThreshold` +- Configure project for automatic releases from Travis diff --git a/src/main/scala/shade/memcached/Codec.scala b/src/main/scala/shade/memcached/Codec.scala index 74994c2..21adf43 100644 --- a/src/main/scala/shade/memcached/Codec.scala +++ b/src/main/scala/shade/memcached/Codec.scala @@ -11,140 +11,191 @@ package shade.memcached -import java.io._ +import java.io.{ByteArrayInputStream, ByteArrayOutputStream, Closeable, ObjectOutputStream} + +import monix.execution.misc.NonFatal +import net.spy.memcached.CachedData +import net.spy.memcached.transcoders._ import scala.annotation.implicitNotFound -import scala.language.implicitConversions +import net.spy.memcached.CachedData.MAX_SIZE + import scala.reflect.ClassTag -import scala.util.control.NonFatal /** - * Represents a type class that needs to be implemented - * for serialization/deserialization to work. - */ -@implicitNotFound("Could not find any Codec implementation for type ${T}. Please provide one or import shade.memcached.MemcachedCodecs._") -trait Codec[T] { - def serialize(value: T): Array[Byte] - def deserialize(data: Array[Byte]): T + * Represents a type class that needs to be implemented + * for serialization/deserialization to work. + */ +@implicitNotFound("Could not find any Codec implementation for type ${T}.") +trait Codec[T] extends Transcoder[T] { + /** + * Returns `true` if the decoding needs to happen asynchronously, + * or `false` otherwise. + * + * Decoding should be marked for asynchrony in case it is + * expensive, for example when compression is applied. + */ + def asyncDecode(d: CachedData): Boolean + + /** + * Encode the given value to a byte array with flags attached, + * meant for storage by the Memcached client. + */ + def encode(value: T): CachedData + + /** + * Decodes byte arrays with flags, as retrieved by the Memcached client, + * into the value it represents. + */ + def decode(data: CachedData): T + + /** Get the maximum size of objects handled by this codec. */ + def getMaxSize: Int } -object Codec extends BaseCodecs - -trait BaseCodecs { - implicit object IntBinaryCodec extends Codec[Int] { - def serialize(value: Int): Array[Byte] = - Array( - (value >>> 24).asInstanceOf[Byte], - (value >>> 16).asInstanceOf[Byte], - (value >>> 8).asInstanceOf[Byte], - value.asInstanceOf[Byte] - ) - - def deserialize(data: Array[Byte]): Int = - (data(0).asInstanceOf[Int] & 255) << 24 | - (data(1).asInstanceOf[Int] & 255) << 16 | - (data(2).asInstanceOf[Int] & 255) << 8 | - data(3).asInstanceOf[Int] & 255 - } +object Codec extends DefaultCodecs - implicit object DoubleBinaryCodec extends Codec[Double] { - import java.lang.{ Double => JvmDouble } - def serialize(value: Double): Array[Byte] = { - val l = JvmDouble.doubleToLongBits(value) - LongBinaryCodec.serialize(l) - } +trait DefaultCodecs extends DefaultCodecsLevel0 { + + import java.lang.{Float => JvmFloat, Double => JvmDouble} + + /** Default codec for `Short`. */ + implicit object ShortBinaryCodec extends GenericIntCodec[Short]( + flags = 2 << 8, // SerializingTranscoder.SPECIAL_INT + toInt = (v: Short) => v.toInt, + fromInt = (v: Int) => v.toShort + ) + + /** Default codec for `Char`. */ + implicit object CharBinaryCodec extends GenericIntCodec[Char]( + flags = 2 << 8, // SerializingTranscoder.SPECIAL_INT + toInt = (v: Char) => v.toInt, + fromInt = (v: Int) => v.toChar + ) + + /** Default codec for `Int`. */ + implicit object IntBinaryCodec extends GenericIntCodec[Int]( + flags = 2 << 8, // SerializingTranscoder.SPECIAL_INT + toInt = (v: Int) => v, + fromInt = (v: Int) => v + ) + + /** Default codec for `Long`. */ + implicit object LongBinaryCodec extends GenericLongCodec[Long]( + flags = 3 << 8, // SerializingTranscoder.SPECIAL_LONG + toLong = (v: Long) => v, + fromLong = (v: Long) => v + ) - def deserialize(data: Array[Byte]): Double = { - val l = LongBinaryCodec.deserialize(data) - JvmDouble.longBitsToDouble(l) + /** Default codec for `Float`. */ + implicit object FloatBinaryCodec extends GenericIntCodec[Float]( + flags = 6 << 8, // SerializingTranscoder.SPECIAL_FLOAT + toInt = JvmFloat.floatToRawIntBits, + fromInt = JvmFloat.intBitsToFloat + ) + + /** Default codec for `Double`. */ + implicit object DoubleBinaryCodec extends GenericLongCodec[Double]( + flags = 7 << 8, // SerializingTranscoder.SPECIAL_DOUBLE + toLong = JvmDouble.doubleToRawLongBits, + fromLong = JvmDouble.longBitsToDouble + ) + + /** Default codec for `Byte`. */ + implicit object ByteBinaryCodec extends Codec[Byte] { + final val FLAGS = 5 << 8 // SerializingTranscoder.SPECIAL_BYTE + + def asyncDecode(d: CachedData): Boolean = false + + def encode(value: Byte): CachedData = { + val bytes = packedUtils.encodeByte(value) + new CachedData(FLAGS, bytes, getMaxSize) } + + def decode(data: CachedData): Byte = + data.getData match { + case null => 0 + case bytes => + packedUtils.decodeByte(bytes) + } + + def getMaxSize: Int = + MAX_SIZE } - implicit object FloatBinaryCodec extends Codec[Float] { - import java.lang.{ Float => JvmFloat } - def serialize(value: Float): Array[Byte] = { - val i = JvmFloat.floatToIntBits(value) - IntBinaryCodec.serialize(i) - } + /** Default codec for `Boolean`. */ + implicit object BooleanCodec extends Codec[Boolean] { + // SerializingTranscoder.SPECIAL_BOOLEAN + final val FLAGS = 1 << 8 + + def asyncDecode(d: CachedData): Boolean = false - def deserialize(data: Array[Byte]): Float = { - val i = IntBinaryCodec.deserialize(data) - JvmFloat.intBitsToFloat(i) + def encode(value: Boolean): CachedData = { + val bytes = packedUtils.encodeBoolean(value) + new CachedData(FLAGS, bytes, getMaxSize) } - } - implicit object LongBinaryCodec extends Codec[Long] { - def serialize(value: Long): Array[Byte] = - Array( - (value >>> 56).asInstanceOf[Byte], - (value >>> 48).asInstanceOf[Byte], - (value >>> 40).asInstanceOf[Byte], - (value >>> 32).asInstanceOf[Byte], - (value >>> 24).asInstanceOf[Byte], - (value >>> 16).asInstanceOf[Byte], - (value >>> 8).asInstanceOf[Byte], - value.asInstanceOf[Byte] - ) - - def deserialize(data: Array[Byte]): Long = - (data(0).asInstanceOf[Long] & 255) << 56 | - (data(1).asInstanceOf[Long] & 255) << 48 | - (data(2).asInstanceOf[Long] & 255) << 40 | - (data(3).asInstanceOf[Long] & 255) << 32 | - (data(4).asInstanceOf[Long] & 255) << 24 | - (data(5).asInstanceOf[Long] & 255) << 16 | - (data(6).asInstanceOf[Long] & 255) << 8 | - data(7).asInstanceOf[Long] & 255 + def decode(data: CachedData): Boolean = + data.getData match { + case null => false + case bytes => + packedUtils.decodeBoolean(bytes) + } + + def getMaxSize: Int = + MAX_SIZE } - implicit object BooleanBinaryCodec extends Codec[Boolean] { - def serialize(value: Boolean): Array[Byte] = - Array((if (value) 1 else 0).asInstanceOf[Byte]) - def deserialize(data: Array[Byte]): Boolean = - data.isDefinedAt(0) && data(0) == 1 - } + implicit def ArrayBinaryCodec: Codec[Array[Byte]] = new Codec[Array[Byte]] { + private[this] val tc = new SerializingTranscoder() - implicit object CharBinaryCodec extends Codec[Char] { - def serialize(value: Char): Array[Byte] = Array( - (value >>> 8).asInstanceOf[Byte], - value.asInstanceOf[Byte] - ) + def asyncDecode(d: CachedData): Boolean = tc.asyncDecode(d) - def deserialize(data: Array[Byte]): Char = - ((data(0).asInstanceOf[Int] & 255) << 8 | - data(1).asInstanceOf[Int] & 255) - .asInstanceOf[Char] - } + def encode(value: Array[Byte]): CachedData = tc.encode(value) - implicit object ShortBinaryCodec extends Codec[Short] { - def serialize(value: Short): Array[Byte] = Array( - (value >>> 8).asInstanceOf[Byte], - value.asInstanceOf[Byte] - ) + def decode(data: CachedData): Array[Byte] = tc.decode(data).asInstanceOf[Array[Byte]] - def deserialize(data: Array[Byte]): Short = - ((data(0).asInstanceOf[Short] & 255) << 8 | - data(1).asInstanceOf[Short] & 255) - .asInstanceOf[Short] + def getMaxSize: Int = tc.getMaxSize } - implicit object StringBinaryCodec extends Codec[String] { - def serialize(value: String): Array[Byte] = value.getBytes("UTF-8") - def deserialize(data: Array[Byte]): String = new String(data, "UTF-8") - } + implicit def StringBinaryCodec: Codec[String] = new Codec[String] { + private[this] val tc = new SerializingTranscoder() + def asyncDecode(d: CachedData): Boolean = tc.asyncDecode(d) + + def encode(value: String): CachedData = tc.encode(value) + + def decode(data: CachedData): String = tc.decode(data).asInstanceOf[String] - implicit object ArrayByteBinaryCodec extends Codec[Array[Byte]] { - def serialize(value: Array[Byte]): Array[Byte] = value - def deserialize(data: Array[Byte]): Array[Byte] = data + def getMaxSize: Int = tc.getMaxSize } + + } -trait GenericCodec { +private[memcached] trait DefaultCodecsLevel0 { private[this] class GenericCodec[S <: Serializable](classTag: ClassTag[S]) extends Codec[S] { - def using[T <: Closeable, R](obj: T)(f: T => R): R = + private[this] val tc = new SerializingTranscoder() + def asyncDecode(d: CachedData): Boolean = + tc.asyncDecode(d) + + def encode(value: S): CachedData = { + if (value == null) throw new NullPointerException("Null values not supported!") + tc.encode(serialize(value)) + } + + def decode(data: CachedData): S = + tc.decode(data) match { + case value: Array[Byte] => deserialize(value) + case _ => throw new NullPointerException("Null values not supported!") + } + + def getMaxSize: Int = + tc.getMaxSize + + private def using[T <: Closeable, R](obj: T)(f: T => R): R = try f(obj) finally @@ -152,30 +203,75 @@ trait GenericCodec { case NonFatal(_) => // does nothing } - def serialize(value: S): Array[Byte] = - using (new ByteArrayOutputStream()) { buf => - using (new ObjectOutputStream(buf)) { out => + private def serialize(value: S): Array[Byte] = + using(new ByteArrayOutputStream()) { buf => + using(new ObjectOutputStream(buf)) { out => out.writeObject(value) out.close() buf.toByteArray } } - def deserialize(data: Array[Byte]): S = - using (new ByteArrayInputStream(data)) { buf => + private def deserialize(data: Array[Byte]): S = + using(new ByteArrayInputStream(data)) { buf => val in = new GenericCodecObjectInputStream(classTag, buf) - using (in) { inp => + using(in) { inp => inp.readObject().asInstanceOf[S] } } } - implicit def AnyRefBinaryCodec[S <: Serializable](implicit ev: ClassTag[S]): Codec[S] = + implicit def serializingCodec[S <: Serializable](implicit ev: ClassTag[S]): Codec[S] = new GenericCodec[S](ev) -} -trait MemcachedCodecs extends BaseCodecs with GenericCodec + /** Helper for building codecs that serialize/deserialize to and from `Long`. */ + class GenericLongCodec[A](flags: Int, toLong: A => Long, fromLong: Long => A) extends Codec[A] { + final val FLAGS = flags + + final def asyncDecode(d: CachedData): Boolean = + false + + final def encode(value: A): CachedData = { + val bytes = packedUtils.encodeLong(toLong(value)) + new CachedData(FLAGS, bytes, MAX_SIZE) + } + + final def decode(data: CachedData): A = + fromLong(data.getData match { + case null => 0 + case bytes => + packedUtils.decodeLong(bytes) + }) + + final def getMaxSize: Int = + MAX_SIZE + } + + /** Helper for building codecs that serialize/deserialize to and from `Int`. */ + class GenericIntCodec[A](flags: Int, toInt: A => Int, fromInt: Int => A) extends Codec[A] { + final val FLAGS = flags + + final def asyncDecode(d: CachedData): Boolean = + false + + final def encode(value: A): CachedData = { + val bytes = packedUtils.encodeInt(toInt(value)) + new CachedData(FLAGS, bytes, MAX_SIZE) + } + + final def decode(data: CachedData): A = + fromInt(data.getData match { + case null => 0 + case bytes => + packedUtils.decodeInt(bytes) + }) + + final def getMaxSize: Int = + MAX_SIZE + } -object MemcachedCodecs extends MemcachedCodecs + protected final val packedUtils = + new TranscoderUtils(true) +} \ No newline at end of file diff --git a/src/main/scala/shade/memcached/FakeMemcached.scala b/src/main/scala/shade/memcached/FakeMemcached.scala index f15411a..25d01f1 100644 --- a/src/main/scala/shade/memcached/FakeMemcached.scala +++ b/src/main/scala/shade/memcached/FakeMemcached.scala @@ -12,6 +12,7 @@ package shade.memcached import monix.execution.CancelableFuture +import net.spy.memcached.CachedData import shade.UnhandledStatusException import shade.inmemory.InMemoryCache @@ -26,7 +27,7 @@ class FakeMemcached(context: ExecutionContext) extends Memcached { case null => CancelableFuture.successful(false) case _ => - CancelableFuture.successful(cache.add(key, codec.serialize(value).toSeq, exp)) + CancelableFuture.successful(cache.add[CachedData](key, codec.encode(value), exp)) } def set[T](key: String, value: T, exp: Duration)(implicit codec: Codec[T]): CancelableFuture[Unit] = @@ -34,52 +35,48 @@ class FakeMemcached(context: ExecutionContext) extends Memcached { case null => CancelableFuture.successful(()) case _ => - CancelableFuture.successful(cache.set(key, codec.serialize(value).toSeq, exp)) + CancelableFuture.successful(cache.set[CachedData](key, codec.encode(value), exp)) } def delete(key: String): CancelableFuture[Boolean] = CancelableFuture.successful(cache.delete(key)) def get[T](key: String)(implicit codec: Codec[T]): Future[Option[T]] = - Future.successful(cache.get[Seq[Byte]](key)).map(_.map(x => codec.deserialize(x.toArray))) + Future.successful(cache.get[CachedData](key).map(codec.decode)) def compareAndSet[T](key: String, expecting: Option[T], newValue: T, exp: Duration)(implicit codec: Codec[T]): Future[Boolean] = - Future.successful(cache.compareAndSet(key, expecting.map(x => codec.serialize(x).toSeq), codec.serialize(newValue).toSeq, exp)) + Future.successful { + val current = cache.get[CachedData](key) + if (current.map(codec.decode) == expecting) { + cache.set(key, codec.encode(newValue), exp) + true + } else { + false + } + } def transformAndGet[T](key: String, exp: Duration)(cb: (Option[T]) => T)(implicit codec: Codec[T]): Future[T] = - Future.successful(cache.transformAndGet[Seq[Byte]](key: String, exp) { current => - val cValue = current.map(x => codec.deserialize(x.toArray)) - val update = cb(cValue) - codec.serialize(update).toSeq - }) map { update => - codec.deserialize(update.toArray) - } + Future.successful(cache.transformAndGet[CachedData](key, exp)(o => codec.encode(cb(o.map(codec.decode))))).map(codec.decode) def getAndTransform[T](key: String, exp: Duration)(cb: (Option[T]) => T)(implicit codec: Codec[T]): Future[Option[T]] = - Future.successful(cache.getAndTransform[Seq[Byte]](key: String, exp) { current => - val cValue = current.map(x => codec.deserialize(x.toArray)) - val update = cb(cValue) - codec.serialize(update).toSeq - }) map { update => - update.map(x => codec.deserialize(x.toArray)) - } + Future.successful(cache.getAndTransform[CachedData](key: String, exp)(o => codec.encode(cb(o.map(codec.decode))))).map(c => c.map(codec.decode)) def increment(key: String, by: Long, default: Option[Long], exp: Duration): Future[Long] = { - def toBigInt(bytes: Seq[Byte]): BigInt = BigInt(new String(bytes.toArray)) - Future.successful(cache.transformAndGet[Seq[Byte]](key, exp) { - case Some(current) => (toBigInt(current) + by).toString.getBytes - case None if default.isDefined => default.get.toString.getBytes + def toBigInt(bytes: Array[Byte]): BigInt = BigInt(new String(bytes)) + Future.successful(cache.transformAndGet[CachedData](key, exp) { + case Some(current) => new CachedData(0, (toBigInt(current.getData) + by).toString.getBytes, Int.MaxValue) + case None if default.isDefined => new CachedData(0, default.get.toString.getBytes, Int.MaxValue) case None => throw new UnhandledStatusException(s"For key $key - CASNotFoundStatus") - }).map(toBigInt).map(_.toLong) + }).map(c => toBigInt(c.getData)).map(_.toLong) } def decrement(key: String, by: Long, default: Option[Long], exp: Duration): Future[Long] = { - def toBigInt(bytes: Seq[Byte]): BigInt = BigInt(new String(bytes.toArray)) - Future.successful(cache.transformAndGet[Seq[Byte]](key, exp) { - case Some(current) => (toBigInt(current) - by).max(0).toString.getBytes - case None if default.isDefined => default.get.toString.getBytes + def toBigInt(bytes: Array[Byte]): BigInt = BigInt(new String(bytes)) + Future.successful(cache.transformAndGet[CachedData](key, exp) { + case Some(current) => new CachedData(0, (toBigInt(current.getData) - by).max(0).toString.getBytes, Int.MaxValue) + case None if default.isDefined => new CachedData(0, default.get.toString.getBytes, Int.MaxValue) case None => throw new UnhandledStatusException(s"For key $key - CASNotFoundStatus") - }).map(toBigInt).map(_.toLong) + }).map(c => toBigInt(c.getData)).map(_.toLong) } def close(): Unit = { diff --git a/src/main/scala/shade/memcached/MemcachedImpl.scala b/src/main/scala/shade/memcached/MemcachedImpl.scala index ab7de5d..efb5034 100644 --- a/src/main/scala/shade/memcached/MemcachedImpl.scala +++ b/src/main/scala/shade/memcached/MemcachedImpl.scala @@ -48,7 +48,7 @@ class MemcachedImpl(config: Configuration, ec: ExecutionContext) extends Memcach case null => CancelableFuture.successful(false) case _ => - instance.realAsyncAdd(withPrefix(key), codec.serialize(value), 0, exp, config.operationTimeout) map { + instance.realAsyncAdd(withPrefix(key), codec.encode(value), exp, config.operationTimeout) map { case SuccessfulResult(givenKey, Some(_)) => true case SuccessfulResult(givenKey, None) => @@ -68,7 +68,7 @@ class MemcachedImpl(config: Configuration, ec: ExecutionContext) extends Memcach case null => CancelableFuture.successful(()) case _ => - instance.realAsyncSet(withPrefix(key), codec.serialize(value), 0, exp, config.operationTimeout) map { + instance.realAsyncSet(withPrefix(key), codec.encode(value), exp, config.operationTimeout) map { case SuccessfulResult(givenKey, _) => () case failure: FailedResult => @@ -97,7 +97,7 @@ class MemcachedImpl(config: Configuration, ec: ExecutionContext) extends Memcach def get[T](key: String)(implicit codec: Codec[T]): Future[Option[T]] = instance.realAsyncGet(withPrefix(key), config.operationTimeout) map { case SuccessfulResult(givenKey, option) => - option.map(codec.deserialize) + option.map(codec.decode) case failure: FailedResult => throwExceptionOn(failure) } @@ -126,8 +126,8 @@ class MemcachedImpl(config: Configuration, ec: ExecutionContext) extends Memcach Future.successful(false) case SuccessfulResult(givenKey, Some((currentData, casID))) => - if (codec.deserialize(currentData) == expectingValue) - instance.realAsyncCAS(withPrefix(key), casID, 0, codec.serialize(newValue), exp, config.operationTimeout) map { + if (codec.decode(currentData) == expectingValue) + instance.realAsyncCAS(withPrefix(key), casID, codec.encode(newValue), exp, config.operationTimeout) map { case SuccessfulResult(_, bool) => bool case failure: FailedResult => @@ -172,10 +172,10 @@ class MemcachedImpl(config: Configuration, ec: ExecutionContext) extends Memcach loop(retry + 1) } case SuccessfulResult(_, Some((current, casID))) => - val currentOpt = Some(codec.deserialize(current)) + val currentOpt = Some(codec.decode(current)) val result = cb(currentOpt) - instance.realAsyncCAS(keyWithPrefix, casID, 0, codec.serialize(result), exp, remainingTime.millis) flatMap { + instance.realAsyncCAS(keyWithPrefix, casID, codec.encode(result), exp, remainingTime.millis) flatMap { case SuccessfulResult(_, true) => Future.successful(f(currentOpt, result)) case SuccessfulResult(_, false) => diff --git a/src/main/scala/shade/memcached/internals/SpyMemcachedIntegration.scala b/src/main/scala/shade/memcached/internals/SpyMemcachedIntegration.scala index 41d49f9..c273ac5 100644 --- a/src/main/scala/shade/memcached/internals/SpyMemcachedIntegration.scala +++ b/src/main/scala/shade/memcached/internals/SpyMemcachedIntegration.scala @@ -178,9 +178,9 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres } } - def realAsyncGet(key: String, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[Array[Byte]]]] = { - val promise = Promise[Result[Option[Array[Byte]]]]() - val result = new MutablePartialResult[Option[Array[Byte]]] + def realAsyncGet(key: String, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[CachedData]]] = { + val promise = Promise[Result[Option[CachedData]]]() + val result = new MutablePartialResult[Option[CachedData]] val op: GetOperation = opFact.get(key, new GetOperation.Callback { def receivedStatus(opStatus: OperationStatus) { @@ -193,7 +193,7 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres def gotData(k: String, flags: Int, data: Array[Byte]) { assert(key == k, "Wrong key returned") - result.tryComplete(Success(SuccessfulResult(key, Option(data)))) + result.tryComplete(Success(SuccessfulResult(key, Option(new CachedData(flags, data, data.length))))) } def complete() { @@ -205,11 +205,11 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres prepareFuture(key, op, promise, timeout) } - def realAsyncSet(key: String, data: Array[Byte], flags: Int, exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Long]] = { + def realAsyncSet(key: String, cachedData: CachedData, exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Long]] = { val promise = Promise[Result[Long]]() val result = new MutablePartialResult[Long] - val op: Operation = opFact.store(StoreType.set, key, flags, expiryToSeconds(exp).toInt, data, new StoreOperation.Callback { + val op: Operation = opFact.store(StoreType.set, key, cachedData.getFlags, expiryToSeconds(exp).toInt, cachedData.getData, new StoreOperation.Callback { def receivedStatus(opStatus: OperationStatus) { handleStatus(opStatus, key, result) { case CASSuccessStatus => @@ -229,11 +229,11 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres prepareFuture(key, op, promise, timeout) } - def realAsyncAdd(key: String, data: Array[Byte], flags: Int, exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[Long]]] = { + def realAsyncAdd(key: String, cachedData: CachedData, exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[Long]]] = { val promise = Promise[Result[Option[Long]]]() val result = new MutablePartialResult[Option[Long]] - val op: Operation = opFact.store(StoreType.add, key, flags, expiryToSeconds(exp).toInt, data, new StoreOperation.Callback { + val op: Operation = opFact.store(StoreType.add, key, cachedData.getFlags, expiryToSeconds(exp).toInt, cachedData.getData, new StoreOperation.Callback { def receivedStatus(opStatus: OperationStatus) { handleStatus(opStatus, key, result) { case CASExistsStatus => @@ -280,9 +280,9 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres prepareFuture(key, op, promise, timeout) } - def realAsyncGets(key: String, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[(Array[Byte], Long)]]] = { - val promise = Promise[Result[Option[(Array[Byte], Long)]]]() - val result = new MutablePartialResult[Option[(Array[Byte], Long)]] + def realAsyncGets(key: String, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Option[(CachedData, Long)]]] = { + val promise = Promise[Result[Option[(CachedData, Long)]]]() + val result = new MutablePartialResult[Option[(CachedData, Long)]] val op: Operation = opFact.gets(key, new GetsOperation.Callback { def receivedStatus(opStatus: OperationStatus) { @@ -298,7 +298,7 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres assert(cas > 0, s"CAS was less than zero: $cas") result.tryComplete(Try { - SuccessfulResult(key, Option(data).map(d => (d, cas))) + SuccessfulResult(key, Option(data).map(d => (new CachedData(flags, data, data.length), cas))) }) } @@ -311,11 +311,11 @@ class SpyMemcachedIntegration(cf: ConnectionFactory, addrs: Seq[InetSocketAddres prepareFuture(key, op, promise, timeout) } - def realAsyncCAS(key: String, casID: Long, flags: Int, data: Array[Byte], exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Boolean]] = { + def realAsyncCAS(key: String, casID: Long, cachedData: CachedData, exp: Duration, timeout: FiniteDuration)(implicit ec: ExecutionContext): CancelableFuture[Result[Boolean]] = { val promise = Promise[Result[Boolean]]() val result = new MutablePartialResult[Boolean] - val op = opFact.cas(StoreType.set, key, casID, flags, expiryToSeconds(exp).toInt, data, new StoreOperation.Callback { + val op = opFact.cas(StoreType.set, key, casID, cachedData.getFlags, expiryToSeconds(exp).toInt, cachedData.getData, new StoreOperation.Callback { def receivedStatus(opStatus: OperationStatus) { handleStatus(opStatus, key, result) { case CASSuccessStatus => diff --git a/src/test/scala/shade/tests/CodecsSuite.scala b/src/test/scala/shade/memcached/CodecsSuite.scala similarity index 77% rename from src/test/scala/shade/tests/CodecsSuite.scala rename to src/test/scala/shade/memcached/CodecsSuite.scala index 412aad8..a48d712 100644 --- a/src/test/scala/shade/tests/CodecsSuite.scala +++ b/src/test/scala/shade/memcached/CodecsSuite.scala @@ -9,22 +9,21 @@ * https://github.com/monix/shade/blob/master/LICENSE.txt */ -package shade.tests +package shade.memcached import org.scalacheck.Arbitrary import org.scalatest.FunSuite import org.scalatest.prop.GeneratorDrivenPropertyChecks -import shade.memcached.{ Codec, MemcachedCodecs } -class CodecsSuite extends FunSuite with MemcachedCodecs with GeneratorDrivenPropertyChecks { +class CodecsSuite extends FunSuite with DefaultCodecsLevel0 with DefaultCodecs with GeneratorDrivenPropertyChecks { /** * Properties-based checking for a codec of type A */ private def serdesCheck[A: Arbitrary](codec: Codec[A]): Unit = { forAll { n: A => - val serialised = codec.serialize(n) - val deserialised = codec.deserialize(serialised) + val serialised = codec.encode(n) + val deserialised = codec.decode(serialised) assert(deserialised == n) } } @@ -46,7 +45,7 @@ class CodecsSuite extends FunSuite with MemcachedCodecs with GeneratorDrivenProp } test("BooleanBinaryCodec") { - serdesCheck(BooleanBinaryCodec) + serdesCheck(BooleanCodec) } test("CharBinaryCodec") { @@ -62,7 +61,11 @@ class CodecsSuite extends FunSuite with MemcachedCodecs with GeneratorDrivenProp } test("ArrayByteBinaryCodec") { - serdesCheck(ArrayByteBinaryCodec) + serdesCheck(ArrayBinaryCodec) + } + + test("ByteBinaryCodec") { + serdesCheck(ByteBinaryCodec) } } \ No newline at end of file diff --git a/src/test/scala/shade/tests/MemcachedTestHelpers.scala b/src/test/scala/shade/tests/MemcachedTestHelpers.scala index 5938561..f8d8b06 100644 --- a/src/test/scala/shade/tests/MemcachedTestHelpers.scala +++ b/src/test/scala/shade/tests/MemcachedTestHelpers.scala @@ -16,7 +16,7 @@ import shade.memcached._ import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration._ -trait MemcachedTestHelpers extends MemcachedCodecs { +trait MemcachedTestHelpers extends DefaultCodecs { val defaultConfig = Configuration( addresses = "127.0.0.1:11211", authentication = None,